Core Data 學習筆記(二)被管理對象模型

目錄

3、被管理對象模型

CoreData的大多數功能依賴於你建立的用於描述實體、實體屬性以及實體間關係的Schema。Schema被表述爲一個被管理對象模型(NSManagedObjectModel)實例對象。一般,這個實例對象越豐富,CoreData對你應用程序的支持越好。html

3.1 被管理對象模型特色

被管理對象模型有以下特色:ios

  • 被管理對象模型是一個NSManagedObjectModel類的實例。
  • 它描述你在應用中使用的數據實體的Schema。

詳情描述以下。git

3.1.1 實體(Entities)

一個模型包含NSEntityDescription對象,該對象用於描述一個實體的名稱,以及在運行時被使用來表達這個類的實體的名稱。一個NSEntityDescription對象還可能包含NSAttributeDescription以及NSRelationshipDescription兩個對象。數據實體可能還包含由NSFetchedPropertyDescription類的實例來表達的可提取屬性。模型還可能有提取請求模板,由NSFetchRequest的實例來表達。sql

注意:編程

  • 在一個模型當中,實體可能被排列在一個繼承結構當中,實體也多是抽象的。

實體繼承swift

對象實體間的繼承與類對象的繼承相似,都是用於將多個實體相同的屬性抽象出來放到父實體當中,從而能夠減小重複建立、維護相同屬性的工做。xcode

注意:數據結構

  • 若是你在代碼中建立實體繼承關係,你必須使用自頂向下的方向進行建立。也就是說,你不能夠先建立一個子實體,而後給這個子實體指定父實體。可是你能夠建立一個父實體,而後爲這個父實體指定子實體(使用.setSubentities方法)。

抽象實體app

能夠指定一個實體爲抽象的,也就是說在應用程序中不會建立這個實體的實例。當在應用中,有大量的類有相同的一些屬性,咱們能夠將這些屬性抽象出來,組成一個抽象實體,而後讓使用這些通用屬性的實體去繼承這個抽象實體。這樣咱們就完成了對於代碼的複用。框架

3.1.2 實體性質(Properties)

一個實體的性質通常是指其屬性(Attribute)以及與其餘實體間的關係,包含提取屬性(Fetched Property)。實體的屬性不能以任何沒有參數的NSObjectNSManagedObject類成員函數名來命名。以免混淆。

臨時屬性是做爲模型的一部分來定義的,可是臨時屬性不會做爲實體數據的一部分來持久化到數據存儲當中。CoreData追蹤你對臨時屬性進行的更改,因此它們被記錄以便回滾操做。

實體屬性

CoreData原生支持多種屬性類型,如字符串、日期、整型。若是你想要使用原生沒有的類型做爲實體屬性的類型,可使用在Non-Standard Persistent Attributes中描述的幾種方法。

能夠設置屬性爲optional的,這樣就能夠不爲實體的這個屬性賦值,也就是說,這個屬性是非必須的。

注意:

  • SQL中的NULL與iOS中的nil不一樣,當一個實體的屬性值爲NULL時,其並表明着iOS中的nil。如,對於數值型來屬性來講,NULL就是NULL,而不是0

實體關係

CoreData支持「1對N」、「1對1」關係。能夠指定實體間關係是否爲「必填」和「基數」,以及關聯實體間的刪除方式。關於實體間關係的更多細節,能夠參考Relationships and Fetched Properties

3.1.3 提取模板

使用NSFetchRequest類來描述從持久化數據存儲中提取數據的請求。能夠在一個被管理對象模型中預約義數據提取請求,這樣你能夠預約義按需對模型進行檢索的查詢。關於提取模板的更多的細節能夠參見Accessing and Using a Managed Object Model at Runtime

3.1.4 用戶信息模板

被管理對象模型實體、屬性、實體關係中的許多元素都有一個關聯的用戶信息字典。你能夠把任何你想要的信息以鍵值對的開工加入到用戶信息字典當中。被置於用戶信息字典中的經常使用的信息包括一個數據實體的版本細節和用於被提取屬性的查詢條件的值。

3.1.5 配置

配置有一個名稱以及一個關聯的實體集。設置項目多是部分重疊的,這是由於一個實體可能在一個或多個配置中出現。可使用.setEntities:forConfiguration來編程實現建立配置。或者使用Xcode的數據模型工具來進行設置。

注意:

  • 一般,當咱們須要將不一樣的實體存到不一樣的數據中時,咱們須要使用配置。
  • 一個持久化存儲協調器只能與一個被管理對象模型進行關聯,因此默認與一個給定的協調器進行關聯的全部存儲都得有一樣的實體。
  • 出於以上限制,能夠建立一個包含有全部想要使用的實體的被管理對象模型,而後就能夠被這個模型所包含的實體所組成的各個子集來建立各類配置。
  • 而後就能夠建立一個協調器來關聯這個模型。這樣,當想要存儲數據的時候,在協調器中的各個配置將會各個不一樣的實體的值保存到不一樣的持久化存儲當中。
  • 必定不能夠建立跨多個持久化存儲的實體關係。

4、被管理對象模型的使用

4.1 建立和加載一個被管理對象模型

咱們可使用Xcode自帶的CoreData模型工具來建立被管理對象,也能夠徹底是使用編程的方式來實現模型的定製。

4.1.1 編譯一個數據模型

使用Xcode工具建立的被管理對象模型有不少在運行時不須要的信息。咱們可使用momc [source] [destination]來編譯被管理對象模型文件(.xcdatamodel),編譯成用於部署的mom文件。

4.1.2 加載一個數據模型

若是使用Xcode建立一個使用CoreData的非文檔型應用程序,應用程序委託包含了檢索數據模型的代碼。模型的名稱(模型文件的名稱)在運行時是不被強制關聯的,因此,一旦數據模型被CoreData加載後,文件名就無關僅要了且也不會被使用了。因此,可使用任意的名稱來命名模型文件。

在建立項目的時候,若是勾選了Use Core Data,則在AppDelegate文件中,將會有以下代碼自動生成:

// MARK: - Core Data stack

lazy var applicationDocumentsDirectory: NSURL = {
    // The directory the application uses to store the Core Data store file. This code uses a directory named "com.46day.CoreDataUsing" in the application's documents Application Support directory.
    let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
    return urls[urls.count-1] as! NSURL
}()

lazy var managedObjectModel: NSManagedObjectModel = {
    // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
    let modelURL = NSBundle.mainBundle().URLForResource("CoreDataUsing", withExtension: "momd")!
    return NSManagedObjectModel(contentsOfURL: modelURL)!
}()

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
    // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
    // Create the coordinator and store
    var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("CoreDataUsing.sqlite")
    var error: NSError? = nil
    var failureReason = "There was an error creating or loading the application's saved data."
    if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil {
        coordinator = nil
        // Report any error we got.
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
        dict[NSLocalizedFailureReasonErrorKey] = failureReason
        dict[NSUnderlyingErrorKey] = error
        error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
        // Replace this with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog("Unresolved error \(error), \(error!.userInfo)")
        abort()
    }
    
    return coordinator
}()

lazy var managedObjectContext: NSManagedObjectContext? = {
    // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
    let coordinator = self.persistentStoreCoordinator
    if coordinator == nil {
        return nil
    }
    var managedObjectContext = NSManagedObjectContext()
    managedObjectContext.persistentStoreCoordinator = coordinator
    return managedObjectContext
}()

// MARK: - Core Data Saving support

func saveContext () {
    if let moc = self.managedObjectContext {
        var error: NSError? = nil
        if moc.hasChanges && !moc.save(&error) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog("Unresolved error \(error), \(error!.userInfo)")
            abort()
        }
    }
}

一般狀況下,咱們不須要去修改這些自動生成的,用於處理被管理數據模型的操做。簡要說明以下。

  • applicationDocumentsDirectory - 標識應用程序存儲數據文件的路徑
  • managedObjectModel - 在應用程序中使用的被管理對象模型
  • persistentStoreCoordinator - 在應用程序中使用的持久化存儲協調器
  • managedObjectContext - 在應用程序中使用的被管理對象上下文
  • saveContext - 用於CoreData保存持久化數據,咱們能夠在此方法中添加一些錯誤處理的操做

若是咱們想要在其餘類當中使用系統自動生成在AppDelegate當中的被管理對象上下文、被管理對象模型等內容,可使用以下方法來獲取一個當前應用的AppDelegate的單例對象。

var delegate = UIApplication.sharedApplication().delegate as? AppDelegate

其中.sharedApplication()方法就是用來獲取當前正在運行的應用程序對象(單例)的。

若是想要本身__手動加載__數據模型文件,可使用下面兩種機制:

  • 使用init?(contentsOfURL url: NSURL)實例方法從一個指定的URL中加載。
  • 使用mergedModelFromBundles類方法來從一個制定的Bundle集合中建立一個數據模型。

注意:

  • 通常來講,在一個應用程序中,使用一個模型文件就足夠了。可是,仍然能夠經過使用多個URL來加載多個模型文件,而後在使用這些模型實例化協調器前,使用modelByMergingModels來合併它們。

4.1.3 若是你的項目包含一個以上的模型,將可能出現的問題

當咱們使用mergedModelFromBundles來手動合併多個數據模型文件時,有是會由於多個文件的版本等問題而產生一些異常錯誤。

  • 若是僅僅是數據模型文件被重命名,CoreData將會嘗試合併如今的以及舊版本的文件,這可能會引發以下錯誤。

reason = "'Can't merge models with two different entities named 'EntityName''";

  • 若是建立一個包含來自原始模型中不一樣實體的模型,CoreData將合併新的和舊的到一塊兒。而若是你已經存儲過一些數據,可能會引發以下錯誤。

reason = "The model used to open the store is incompatible with the one used to create the store";

針對以上異常,提出以下的解決的方法:

  • 在運行程序以前,清空以前編譯的舊的產品文件。
  • 使用init?(contentsOfURL url: NSURL)替代使用mergedModelFromBundles

4.2 修改模式(Schema)使得一個模型與舊的存儲衝突

由於模型用於描述數據存儲的數據結構,因此修改模型的任意一個部分都將修改模式(Schema),從而引發與以前建立的模型的衝突。若是修改了模式,那麼就須要將舊的數據遷移到新的數據存儲當中,參考(Core Data Model Versioning and Data Migration Programming Guide)。

注意:

若是想要修改模型,可是還想要保留打開以前建立的舊版本模型的能力,則_必須保留舊版本模型_。CoreData沒法打開不兼容的數據模型,因此,若是想要修改數據模型,可是還要保證可以打開已經存在的數據存儲,須要作到:

  • 設置模型版本
  • 在編輯模式以前,給當前模型建立一個新的版本
  • 編輯模型的新版本,保持模型的就版本不變。

4.3 在運行時訪問和使用一個被管理對象模型

可使用以下方式在運行時獲得被管理對象模型,以便使用這個模型。

  • 從被管理對象上下文取得被管理對象模型

let model = self.managedObjectContext?.persistentStoreCoordinator?.managedObjectModel

  • 從被管理對象實體取得被管理對象模型

let model = managedObject.entity.managedObjectModel

4.3.1 編程實現提取請求模板

首先,咱們須要取得當前使用的數據模型。而後,建立請求以及請求所使用的查詢字符串。最後,使用setFetchRequestTemplate方法將請求加入到模型型當中。

let model = self.managedObjectContext?.persistentStoreCoordinator?.managedObjectModel

var requestTemplate = NSFetchRequest(entityName: "User")
var predicateTemplate = NSPredicate(format: "name like aaron", argumentArray: nil)
requestTemplate.predicate = predicateTemplate

model?.setFetchRequestTemplate(requestTemplate, forName: "UserNameLikeAaron")

4.3.2 訪問提取請求模板

在訪問提取請求模板時,傳入的參數必須包含在請求模板當中所使用到的全部實體的鍵值。若是想要測試NULL,則須要傳入nil類型對象。具體參考Predicate Format String Syntax

let model = self.managedObjectContext?.persistentStoreCoordinator?.managedObjectModel

var request = model?.fetchRequestTemplateForName("UserNameLikeAaron")
var results = self.managedObjectContext?.executeFetchRequest(request!, error: nil)

注意:

若是請求模板的查詢條件當中,沒有參數的須要,則在訪問請求模板時,必須作到:

  • 使用fetchRequestFromTemplateWithName:substitutionVariables,而後傳入nil做爲參數。
  • 或者,使用fetchRequestTemplateForName,而後copy結果。

參考

官方教程

網摘文章

相關文章
相關標籤/搜索