本節書摘來自華章出版社《core data應用開發實踐指南》一書中的第3章,第3.4節,作者 (美)tim roadley,更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視
有時候我們需要比輕量級遷移更為精細的控制手段。比方說,我們要把measurement實體替換成另外一個名叫amount的實體,并且還想把measurement實體中名叫abc的那個屬性遷移到amount實體中的xyz 屬性上面。abc中已有的資料也要遷移到xyz屬性。為了完成這些需求,開發者需要建立模型映射,以便手工指明映射關系。在添加持久化存儲區時,即便nsinfermappingmodelautomaticallyoption選項設為yes,core data也還是會先檢測有沒有檔案,如果有的話,那麼在執行自動推斷之前,它會先試着使用這個檔案來遷移。在測試映射模型之前,建議先禁用該選項,這樣才可以确定映射模型是不是已經付諸使用并且能夠正常運作了。
請按下列步驟修改grocery dude,以禁用自動化模型映射功能:
修改coredatahelper.m檔案中的loadstore方法,把nsinfermappingmodelautomaticallyoption設為@no。
請按下列步驟修改grocery dude,以便添加新模型,為從measurement實體遷移到amount實體做準備:
可以先抓取一份快照或備份整個項目。
根據model 2版本來建立新版模型,将其命名為model 3。
選中model 3.xcdatamodel。
删除measurement實體。
建立amount實體,并向其中添加類型為string的xyz屬性。
根據amount實體建立nsmanagedobject子類。在儲存類檔案這個步驟中,别忘了勾選targets中的“grocery dude”。
将model 3設為目前模型版本。
運作應用程式,目前它應該出錯并崩潰。錯誤資訊如圖3-5所示。

為了解決圖3-5中的錯誤,我們需要建立映射模型,以指明字段之間的映射關系。具體到本例來說,就是要把舊模型中measurement實體的abc屬性遷移為新模型中amount實體的xyz 屬性。
請按下列步驟修改grocery dude,以添加新的映射模型:
確定data model 組處于選中狀态。
點選file>new>file...菜單項。
選擇ios>core data>mapping model,并點選next按鈕。
把model 2.xcdatamodel選為source data model,并點選next按鈕。
把model 3.xcdatamodel選為target data model,并點選next按鈕。
将mapping model的名稱設為model2tomodel3,并将其儲存。
確定targets中的“grocery dude”處于勾選狀态,然後點選create按鈕。
選中model2tomodel3.xcmappingmodel。
現在你将看到如圖3-6所示的model-mapping editor界面。
xcode目前呈現的這套映射是core data以最合理的方式推斷出來的。在界面左方,應該會看到entity mappings字樣,它下面列出了源實體與目标實體之間的映射。通過圖3-6我們應該可以看到,core data已經推斷出源item實體對應于目标item實體,而這個推斷是合理的。實體映射時所采用的命名标準是sourcetodestination(源實體名到目标實體名)。明白了這一點之後,我們就會發現,amount實體并沒有與之對應的源實體,因為amount沒有出現在源模型裡面。
請按下列步驟修改grocery dude,以便将舊版模型的measurement實體映射到新版模型的amount實體:
確定model2tomodel3.xcmappingmodel處于選中狀态。
在entity mappings中標明amount。
點選view>utilities>show mapping model inspector菜單項(假如菜單裡沒有這一項,可以按“option++3”組合鍵),然後應該就會看到如圖3-7所示的面闆了。
在entity mapping區域中,把amount實體的source設定成measurement。設定好的結果如圖3-7所示。
由于我們把measurement選為源實體,而把amount選為目标實體,是以mapping name這一欄就自動變成了measurementtoamount。此外,映射的type(類型)也從add變成了transform。如果要實作更為複雜的遷移方式,那麼可以在custom policy文本框中輸入類名,這個類應該是nsentitymigrationpolicy的子類。在該子類中,可以通過覆寫createdestinationinstancesforsourceinstance方法而操作待遷移的資料。比方說,可以攔截abc這個屬性的值,将其中每個單詞的首字母改為大寫,然後再把修改過的值遷移到xyz屬性。
圖3-7底部的source fetch選項可通過謂詞(在filter predicate文本框中輸入)限定遷移過來的資料量。假如隻想把舊資料中的一部分遷移過來,那麼這個選項就很有用了。此處的謂詞格式與通常代碼中編寫的謂詞相似,隻不過要用$source變量來表示源資料。比方說,如果想把abc屬性為nil的源資料排除掉,那麼可将謂詞寫成$source.abc!=nil。
在前述的圖3-6中,標明entity mappings字樣下方的itemtoitem實體,并觀察屬性映射中的内容,會看到目标實體中的每個屬性都設定有對應的value expression。現在再來檢視measurementtoamount實體的映射,會發現xyz這個destination 屬性并沒有設定value expression。這就意味着xyz屬性目前還沒有對應的source 屬性,需要按照itemtoitem實體映射中的那種格式,給它設定一條value expression。我們一開始提出的需求是把abc屬性映射到xyz屬性,是以接下來就按照這個需求配置value expression。
請按下列步驟修改grocery dude,給名為xyz的destination 屬性設定适當的value expression:
在measurementtoamount的實體映射界面中,把xyz這個destination 屬性的value expression設定為$source.abc。
遷移模型雖然已經配置好了,但demo方法仍然會從measurement實體擷取資料,而在新模型中,是沒有這個實體的。
請按下列步驟修改grocery dude,令demo方法使用amount實體而非measurement實體:
把appdelegate.m檔案頂部的#import "measurement.h"替換為#import "amount.h"。
修改appdelegate.m檔案的demo方法,用程式清單3-4中的代碼替換原有代碼。原來的代碼是擷取一小部分measurement樣例資料,而這段代碼也與之相似,它是擷取一小部分amount資料。
運作應用程式。由于要遷移資料,是以加載螢幕的顯示時間可能會稍微長一些,具體情況與電腦速度有關。
隻要遷移過程順利完成,程式就不會崩潰,你應該會在控制台的日志中看到如圖3-8所示的資訊。
為了驗證遷移後的資料是否已經儲存到持久化存儲區,我們可以用第2章中講過的辦法來檢視grocery-dude.sqlite檔案的内容。正确的結果應該如圖3-9所示,其中多出了名為zamount的表(這張表對應于amount實體),舊的measurement實體裡的資料現已出現在這張表中。
在學習下一節之前,一定要先關掉sqlite database browser。