標籤:
Core Data版本遷移基礎
通常,在使用Core Data的iOS App上,不同版本上的資料模型變更引發的資料移轉都是由Core Data來負責完成的。
這種資料移轉模式稱為Lightweight Migration(可能對於開發人員來說是lightweight),開發人員只要在添加Persistent Store時設定好對應選項,其它的就交付給Core Data來做了:
從命名上可以看出這兩個選項分別代表:自動遷移Persistent Store,以及自動建立Mapping Model。
自動遷移Persistent Store很好理解,就是將資料從一個物理檔案遷移到另一個物理檔案,通常是因為物理檔案結構發生了變化。
自動建立Mapping Model是為遷移Persistent Store服務的,所以當自動遷移Persistent Store選項NSMigratePersistentStoreAutomaticallyOption為@(YES)、且找不到Mapping Model時,coordinator會嘗試建立一份。
其它初始化情境可以參考Initiating the Migration Process。
既然是嘗試建立,便有成功和失敗的不同結果。只有當資料模型的變更屬於某些基本變化時,才能夠成功地自動建立出一份Mapping Model,比如:新增一個欄位;刪除一個欄位;必要欄位轉換成可選欄位;可選欄位轉換成必要欄位,同時提供了預設值等等。
因為可能建立Mapping Model失敗,所以考慮容錯性的話,可以事先判斷下能否成功推斷出一份Mapping Model:
利用如上類方法,如果無法建立一份Mapping Model,則會返回nil,並帶有具體原因。
以上都建立在Core Data能夠自動找到sourceModel和destinationModel的基礎上,如果無法找到對應的兩份Model,則需要開發人員手工建立NSMigrationManager來進行資料移轉(可以參考Use a Migration Manager if Models Cannot Be Found Automatically)。
版本遷移過程
那麼,資料移轉的過程是如何進行的?
首先,發生資料移轉需要三個基本條件:可以開啟既有persistent store的sourceModel,新的資料模型destinationModel,以及這兩者之間的映射關係Mapping Model。
利用這三樣,當調用如下代碼時(addPersistentStore):
Core Data建立了兩個stack(分別為source stack和destination stack,可以參考Core Data stack),然後遍曆Mapping Model裡每個entity的映射關係,做以下三件事情:
1. 基於source stack,Core Data先擷取現有資料,然後在destination stack裡建立當前entity的執行個體,只填充屬性,不建立關係;
2. 重新建立entity之間的關係;
3. 驗證資料的完整性和一致性,然後儲存。
考慮到第二步是重建立立entity之間的關係,那麼應該是在第一步就把所有entity的對象都建立好了,並且保留在記憶體中,為第二步服務(事實上也是如此)。
完成第二步後,所有資料還是維持在記憶體中(可能還有兩份,因為有兩個stack),在完成資料驗證後才真正儲存。
這樣的話,會容易導致記憶體佔用過多,因為Core Data在這個遷移過程中也沒有一種機制清理響應的context。所以在資料量較多時,App可能會遇到在資料移轉過程因為記憶體緊張而被系統幹掉。
針對這種情況,我們可以自訂遷移過程。
自訂資料移轉(解決記憶體問題)
自訂資料移轉的過程通暢分為三步:
第一步是判斷是否需要進行資料移轉:
第二步是建立一個Migration Manager對象:
第三步是真正發生資料移轉:
上面三幅圖所展示的代碼在記憶體使用量量上跟lightweight migration也沒什麼區別,無法解決記憶體峰值過高的問題。
雖然Core Data專家Marcus S. Zarra比較傾向堅持使用lightweight migration,不過對於上述記憶體佔用過多的問題,Apple官方推薦使用Multiple Passes來解決。
關於Multiple Passes,官方文檔的說明很簡明扼要,如有需要,可以參考Stackoverflow上的這麼一篇文章。
用我的話往簡單裡說就是對資料模型進行劃分,把一份Mapping Model拆分成多份,然後分成多次遷移,從而降低記憶體峰值。這需要對資料庫進行全盤的考慮(甚至可能需要變更部分設計),然後通過合理的劃分把相關聯的Entity放在一份Mapping Model裡面(因為要建立關聯)。
新的問題
採用上述方案來解決資料移轉過程中記憶體峰值的問題,我們仍然需要關注遷移所耗費的時間、記憶體,從而能夠在資料上驗證方案的有效性,並且在使用者互動方面進行一些必要的更改(總不能讓使用者傻傻地在那邊等資料移轉吧)。
雖然可以解決記憶體峰值的問題,但也引進了其它問題。
1. 需要對資料模型進行劃分(以及變更),存在一定的工作量和風險;
2. 需要手工建立多份Mapping Model;
3. 需要手工編寫Multiple Passes遷移代碼;
4. 需要在每個版本變遷中都再次建立新的Mapping Model,且在跨版本遷移過程存在著其它問題;
5. 資料模型版本多起來,就面臨著跨版本遷移的問題,是要為每個曆史版本建立到最新模型的Mapping Model,還是只維護最近兩個版本的Mapping Model(更早的版本通過相鄰版本的Mapping Model依次遷移過來,比較耗時)?
6. 對資料模型重新劃分後,無關的Entity簡單變更也會引起整個store和model的不相容,需要遷移,那麼是否考慮分庫?
7. 這麼大的動作服務的使用者數是很少的(只有少數使用者會遇到,或者是很少),但卻是比較資深的(因為訊息記錄多),疼。。。
8. 這無法解決單個Entity資料量過大的問題,針對這種情境,只能自己手工編碼進行小批量的資料移轉;
CoreData版本遷移