數據不可變之linked-in/rocketdata

背景

在咱們一般的數據可變的數據框架中,咱們從 db 讀取的數據放在 cache 裏面供上層業務調用。好比一個 book 對象,若是咱們在上層業務中有多個地方都須要用到這個 book 對象,那麼其實咱們是直接在引用這個對象。若是咱們須要修改 book 對象的 islike 屬性,咱們會直接調用進行修改html

book.islike = yes

這樣會存在什麼問題呢?git

試想一下多線程的狀況,咱們在 thread1 在讀取 book 對象的 islike 屬性,thread2 在修改 book 的 islike 屬性,這兩個操做同時發生,這個時候就會致使 crash。github

怎麼解決這種問題?編程

結果方案有以下幾種網絡

  1. 加鎖:atomic 屬性,可是這樣毫無疑問會嚴重印象到 app 的總體效率,畢竟全部的讀取都是在鎖的環境下進行了。多線程

  2. 對象實行線程隔離。好比 realm ,在每個線程中,它都保有一份被引用對象的線程快照,不一樣線程間的數據是獨立的,同時讀寫並不會形成多線程的問題。app

  3. 數據不可變,好比本文將要介紹的 rocketdata ,多線程的問題出如今對同一個對象同時寫,或同時讀寫。數據不可變要求全部從 db 讀出來的對象不容許在上層對它直接進行修改,只能讀取,當你要修改一個對象的時候,你須要生成一個全新的對象,對這個全新對象進行修改,而後替換掉全部用到的舊對象,這樣就能杜絕同時讀寫以及同時寫的操做了。框架

下面講重點分析不可變解決方案 rocketdata 的實現。ide

不可變對象須要面臨的問題

由於咱們從 db 讀出來的數據,有可能被上層直接指針引用,也可能被拷貝出去,這些對象分散在不一樣的場景,所以不可變對象的使用中,一個很大的問題就在於當一個對象被改變的時候,如何去更新整個 app 中全部持有該對象的舊對象。fetch

rocketdata的方案

rocketdata 爲了實現對象的更新,它對全部的業務層對象都進行了一層包裝,提供了 DataProvider 對單個對象的封裝, CollectionDataProvider 對列表數據的封裝,真實的數據存在於這些 provider 裏面。

當咱們設置 datasource 的時候,provider 不只會保存當前數據,還會監聽當前數據源裏面全部的數據,包括這些數據的子數據。好比咱們有一個 books 列表,每個 book 裏面都有一個 author 對象,那麼在設置這個 books 列表的時候,provider除了將 books 保留以外,還會監聽每個 book 對象以及每個 book 對象裏面的 author,而且通知全部監聽這些對象的其餘 provider 去更新相應的數據。

以CollectionDataProvider爲例,當咱們網絡請求回來數據的時候,咱們調用以下接口更新當前 datasource

NetworkManager.fetchChats { (models, error) in
            if error == nil {
                self.dataProvider.setData(models, cacheKey: self.cacheKey)
                self.tableView.reloadData()
            }
        }

setData 作了什麼呢?下面是它的簡化代碼

open func setData(_ data: [T], cacheKey: String?, shouldCache: Bool = true, context: Any? = nil) {
 
        self.dataHolder.setData(data, changeTime: ChangeTime())
 ....       updateAndListenToNewModelsInConsistencyManager(context: context)
 }

它作了兩件事,一個是更新當前CollectionDataProvider的數據,另一個就是更新其餘監聽了這些數據的provider,同時監聽新的對象。

須要說明,它這個監聽並非咱們普通意義上的 addobserver 或者 kvc , rocketdata 建議全部的 provider 都持有一個 datamodlemanager 單例,datamodlemanager 包含了一個 consistencyManager ,consistencyManager 負責同步並持有一個 listeners 字典,裏面紀錄了全部的監聽。

監聽作的事情,就是爲每個 modelIdentifier 記錄一個列表,這個列表保存了全部監聽的 provider(這個 modelIdentifier 對於 model 而言至關於主鍵,每一個 provider 也會有一個本身的modelIdentifier,用於區分不一樣的對象),當一個特定modelIdentifier 的 model 更新的時候,根據listener[modelIdentifier] 找到全部的監聽者,並進行更新,簡單代碼以下。

private func addListener(_ listener: ConsistencyManagerListener, recursivelyToChildModels model: ConsistencyManagerModel) {
        // Add this model as listening
        addListener(listener, toModel: model)
        // Recurse on the children
        model.forEach() { child in
            self.addListener(listener, recursivelyToChildModels: child)
        }
    }
private func addListener(_ listener: ConsistencyManagerListener, toModel model: ConsistencyManagerModel) {
        let id = model.modelIdentifier
        if let id = id {
            var weakArray: WeakListenerArray = {
               ...
            }()
            let alreadyInArray: Bool = {
                ...
            }()
            if !alreadyInArray {
                weakArray.append(listener)
                listeners[id] = weakArray
            }
        }
    }

當咱們更新數據的時候,咱們就拿這些改變的數據去找全部監聽的 provider

open func updateModel(_ model: ConsistencyManagerModel, context: Any? = nil) {
        dispatchTask { cancelled in
            let tuple = self.childrenAndListenersForModel(model)
            ...  
            self.updateListeners(tuple.listeners, withUpdatedModels: optionalModelUpdates, context: context, cancelled: cancelled)
        }
    }

這裏的 childrenAndListenersForModel 就是找到當前變動對象的子對象以及 consistencyManager 裏面的監聽列表,代碼以下:

private func childrenAndListenersForModel(_ model: ConsistencyManagerModel, modelUpdates: DictionaryHolder<String, [ConsistencyManagerModel]>, listenersArray: ArrayHolder<ConsistencyManagerListener>) {

        if let id = model.modelIdentifier {
            //modified model
            modelUpdates.dictionary[id] = projections
            
            //listeners to id (need avoid repeat listener)
            listenersArray.array.append(listeners[id])  
                }

        model.forEach { child in
            self.childrenAndListenersForModel(child, modelUpdates: modelUpdates, listenersArray: listenersArray)
        }
    }

找到當前變化的 models 以及相關的全部 listeners 後,就能夠開始真正的更新過程。

private func updateListeners(_ listeners: [ConsistencyManagerListener], withUpdatedModels updatedModels: [String: [ConsistencyManagerModel]?], context: Any?, cancelled: ()->Bool) {
  For each listener:
     1. Gets the current model from listener
     2. Generates the new model.
     3. Generates the list of changed and deleted ids.
     4. Ensures that listener listens to all the new models that have been added.
     5. Notifies the listener of the new model change.

 }

上面作的事情就是遍歷全部的待更新的 listeners ,並對 listener 持有的數據( currentModel )與更新的數據進行比較,看其中的數據是否發生了變化,若是有變化,則進行替換。這個替換是以listener 爲粒度進行的,也就是若是你更新多個 models ,而後這多個 models 和某個 provider 關聯,那麼其實這些 mdels 的更新是一次性進行的。更新代碼以下

open func modelUpdated(_ model: ConsistencyManagerModel?, updates: ModelUpdates, context: Any?) {
...
 dataHolder.setData(newData, changeTime: changeTime ?? ChangeTime())
 ...
}

上面的方法是在 provider 裏面執行的,它利用 updates 生成新的 newdata ,而後替換掉當前 provider 所持有的 data 數據。

更改完成後,經過回調通知相應的controller,刷新界面

func collectionDataProviderHasUpdatedData<T>(_ dataProvider: CollectionDataProvider<T>, collectionChanges: CollectionChange, context: Any?) {
        self.tableView.reloadData()
}

結束語

rocketdata 很是好的一點是它包裝了全部的通知以及更新過程,你不須要手動的去註冊各類同志,而且不用擔憂通知遺漏。不過使用這套東西,對於編程習慣也是一種不小的挑戰,要想真正運用到本身的項目,還有有必定挑戰的。

相關文章
相關標籤/搜索