本文部分內容來自 Objc.io 的《Core Data》一書,買來一個月后覺得39美元總體還是花得值得的,推薦購買。
Fetch requests 并非獲取 managed objects 的唯一途徑,而且,應該盡可能避免 fetch。因為 fetch 操作會遍歷整個 Core Data Stack,代價很大,是重大的性能瓶頸。獲取 managed objects 的另外一個重要途徑是 relationship。
Creating Relationships
一般關系
Relationship 有三種:一對一(to-one),一對多(to-many),多對多(many-many)。多對多關系的對應關系也應該是多對多關系。建立關系時盡量避免單向關系,這樣不利于 Core Data 維護對象之間的關系。在 Model Editor 里設置關系時注意設置逆向關系 Inverse Relationship,這樣 Core Data 可以替我們完成很多工作。
部門 Department 和職員 Employee 的關系設置:
這里部門與職員的關系是一對多,職員與部門的關系是一對一。
一對一關系和 managed object 的其他屬性沒有多大區別,Xcode 生成的子類里該關系的屬性類型就是關系目標的類型;一對多關系里,為了保持維護的對象唯一,子類使用 Set 來維護對對象的引用。若勾選了 Ordered,則使用 NSOrderdSet 類。使用有序關系并沒有真的對里面的對象進行排序,只是方便了索引。
extension Department {
@NSManaged var name: String?
@NSManaged var employees: NSOrderedSet?
}
extension Employee {
@NSManaged var name: String?
@NSManaged var department: Department?
}
不一般關系⊙▂⊙
上面的例子里關系目標都是其他實體 Entity,關系也可以指向自身類型的 Entity,比如,利用 Person 建立一個族譜,那么關系的目標對象都是 Person。這里除了關系引用的是自身類型,也沒有什么特別的了。
還有一種比較特別:單向關系。上面也提到了,盡量不要建立單向關系。因為在單向關系里,一方被刪除了的話,另一方無法得知。使用單向關系時必須小心。
PS: Core Data 不支持跨 Store 的關系。
Accessing and Manipulating Relationships
訪問關系
訪問關系有兩種途徑,一種是和訪問普通的對象屬性一樣:
let employees: NSOrderedSet = aDepartment.employees
let department: Department = anEmployee.department
另外一種是使用 KVC 方法,如果傳遞的 key 不是 Modal 里定義的屬性,將會拋出異常:
let employees = aDepartment.valueForKey("employees") as? NSOrderedSet
let department = anEmployee.valueForKey("department") as? Department
除此之外,關系還支持 keypath 訪問,path 支持普通的屬性 perporty 和關系 relationship:
let departmentName = anEmployee.valueForKeyPath("department.name") as? String
修改關系
修改關系這件事需要好好說明一下:我們只需要修改關系一方,Core Data 會自動替我們處理好剩下的事情。比如下面的情況:
只需要:
//方法1:
anEmployee.department = newDepartment
//方法2:
anEmployee.setValue(newDepartment, forKey:"department")
或者:
//如果沒有勾選 Ordered 選項,使用 mutableSetValueForKey(_:)
newDepartment.mutableSetValueForKey("employees").addObject(employee)
//如果勾選了,使用 mutableOrderedSetValueForKey(_:)
newDepartment.mutableOrderedSetValueForKey("employees").addObject(employee)
只需要使用上面的一種方法就可以了。
如果像批量更改部門的職員構成怎么辦,單個移除以及添加很麻煩,使用 KVC 方法。
newDepartment.setValue(newEmployees, forKey:"employees")
在 Department 這一端,因為直接訪問employees
得到的一個無法更改的量,只能使用mutableSetValueForKey(_:)
或mutableOrderedSetValueForKey(_:)
來進行個體的修改,或者使用setValue(_, forKey:)
來進行整體的修改。處于性能的原因,set<Key>:這類方法比如setEmployees:
不能用來修改關系。
NSManagedObject
重寫了valueForKey:
和setValue:forKey:
以及mutableSetValueForKey(_:)
這三個 KVC 方法,當 key 不是在 modal 里定義的屬性時,這三個方法都會拋出異常。你不應該在子類中重寫這三個方法。
Delete Rule
上面只需要在修改一端修改關系剩下的事情由 Core Data 替我們處理了得益于 Delete Rule 的設計。刪除規則決定了刪除對象時它的關系怎么處理的行為。Core Data 提供了四種刪除規則,下面還是用部門與員工之間的關系來舉例:
- 拒絕 Deny
如果關系目標里還有對象,比如要刪除(撤銷)某個部門,但該部門還有一個員工,那么是無法刪除(撤銷)該部門的,只有該部門里所有的員工被調往其他部門或是被刪除(解雇)了才能刪除(撤銷)該部門。 - 失效 Nullify
移除對象之間的關系但是不刪除對象。只有當一方關系是可有可無的時候才有意義,比如員工有沒有部門都無所謂,那么刪除該對象時只會將其關系設置為空而不會刪除對象本身。 - 連坐 Cascade
在這種規則下,可以把一個部門一鍋端。刪除(撤銷)某部門,部門里的員工也會被全部解雇(刪除)。 - 不作為 No Action
什么也不做。部門被刪除(撤銷)后,里面的員工還不知道,以為自己還在這個部門工作呢。
前三種刪除規則還是比較清晰的,都有合適的使用場景,而最后一種比較危險,需要你自己來確定關系里的對象是否還存在。
Relationship Faults
訪問 managed object 的 relationship property 時,relationship 對應的 managed object 如果在 context 中不存在,那么被 fetch 進內存時會處于 faults 狀態,即使已經存在也不會主動填充 managed object 中的數據,無論原來的 managed object 處于 faults 狀態還是已經填充了全部數據。而且,無論是 to-one relationship 還是 to-mant relationship都是這樣。這個特性對于維持較低的內存占用具有重要意義。
Relationship faults 有兩層:訪問 relationship 時,這時候 Core Data 做的僅僅是根據 relationship 的 objectID 來獲取相應的 managed object,并不會填充數據;訪問 relationship 上的某個屬性時,relationship 才會填充該屬性對應的數據。
Reference Cycles
關系一般都是雙向的,而且關系并不想其他對象一樣有強引用和弱引用的區別,在這種情況下,當關系的雙方都在內存中后,自然而然就形成了引用循環。打破引用循環的唯一方法是刷新關系中的一方,使用 context 的refreshObject(_:mergeChanges:)
來刷新對象。
context.refreshObject(managedObejct, mergeChanges:false)
參數mergeChanges
為 false 時,對象會重新進入 faults 狀態,這會移除對象里所有的數據,包括與其他對象之間的關系。需要注意的是,在這個參數配置下,該方法會放棄對象身上所有沒有保存的變化。
至于何時打破引用循環這取決于應用自身的需要。比如,將當前的 ViewController 從 stack 中移除,你不再需要這些數據了,可以將對象轉變為 faults狀態;或者應用進入后臺,你也可以這樣做降低內存占用避免被系統殺掉。