天天看點

CoreData模闆代碼分析

之前寫過一篇CoreData的簡單使用的文章,其Demo使用了Xcode中的CoreData模闆(建立一個Empty模闆的工程時,如果選擇了Use Core Data選項,那麼建立出來的工程便會附帶上Core Data的代碼)

但是之前并沒有留意CoreData自動引入的模闆代碼的作用,最近又接觸到了CoreData的内容,是以回頭看了一下這部分代碼。

首先看看Core Data的架構結構(下圖來自Core Data Overview):

CoreData模闆代碼分析

以下是我的對CoreData的初級認識:

1.在CoreData中有一個托管對象上下文(ManagedObjectContext),可以把它看做一個緩沖區,負責記錄托管對象的狀态和給出使用者查詢的對象等。

2.在上下文中存儲的是各個托管對象(ManagedObject),也就是我們要持久化的對象資料,它們由托管對象模型(ManagedObjectModel)描述,其模型可以通過xcdatamodeld檔案定制,xcdatamodeld檔案在程式啟動後加載到Bundle中并被解釋成momd檔案夾中的内容。

3.有多種方式來持久化對象資料,如SQLite資料庫、XML檔案、二進制檔案等,這需要和底層的SQLite及File System打交道,是以要用到大量的SQL語句,使程式開發難度加大。幸好CoreData為我們給出了解決方案:持久化倉庫統籌者(PersistentStoreCoordinator),CoreData将與底層的資料庫和檔案系統抽象成一個持久化倉庫(PersistentStore),可以使用持久化倉庫統籌者對各個倉庫進行管理和操作,開發者并不需要關注資料的存儲方式和操作查詢等具體過程,此時持久化倉庫統籌者就像一個ORM的角色,是以不要把CoreData簡單地了解成一個ORM的架構。

要使用CoreData的相關類,首先要在項目中添加CoreData.framework并導入相關的頭檔案,在Use Core Data的項目中已經在pch預編譯頭檔案中導入:

#import <Availability.h>

#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    #import <CoreData/CoreData.h>
#endif
           

下面是AppDelegate.h中自動生成的代碼:

// managedObjectContext,managedObjectModel,persistentStoreCoordinator全部是一次性初始化的

// 托管對象上下文,其中包含多個托管對象
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

// 托管對象模型:用來描述托管對象
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;

// 持久化倉庫統籌者:用來協調托管對象的存儲過程和具體存儲方式
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

// 将托管對象上下文寫入到持久化倉庫中
- (void)saveContext;

// 擷取程式Documents目錄
- (NSURL *)applicationDocumentsDirectory;
           

AppDelegate.m檔案内容見下文。

首先是AppDelegate中的幾個方法,隻有applicationWillTerminate方法中有和CoreData相關的代碼:

- (void)applicationWillTerminate:(UIApplication *)application {
    // Saves changes in the application's managed object context before the application terminates.
    [self saveContext];
}
           

在程式将要終止時,要調用self的saveContext方法儲存托管對象上下文。

saveContext方法實作如下:

- (void)saveContext {
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        // 在上下文發送了變化的情況下才儲存上下文,如果儲存出錯就報錯并終止程式運作
        if ([managedObjectContext hasChanges] && ![managedObjectContext 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();
        }
    }
}
           

注意

managedObjectContext hasChanges] && ![managedObjectContext save:&error]
           

這一行表明,隻有上下文發生了變化才執行後面的save方法。如果save方法傳回NO,那麼儲存失敗,應該實作出錯處理的代碼。

然後是托管對象上下文的getter方法:

- (NSManagedObjectContext *)managedObjectContext {
    // lazy initialization的getter方法
    // 此時在類中通路屬性時,應該使用getter方法來擷取(self.xx),不要直接使用執行個體變量(_xx)
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }
    
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        // 初始化托管對象上下文,并将其與持久化倉庫統籌者相關聯
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}
           

這裡getter方法使用了lazy initialization的方式初始化,也就是一次性的初始化,是以在AppDelegate.m中,不能通過執行個體變量(即_managedObjectContext)的方式來擷取,隻能通過getter方法(self.managedObjectContext或[self managedObjectContext])來擷取,否則該屬性可能沒有被初始化。

在建立一個托管對象上下文對象後,還要将其和一個持久化倉庫統籌者相關聯,實際上統籌者已經和各個持久化倉庫相關聯,這裡就搭起了上下文到各個倉庫之間的橋梁。

然後是持久化倉庫統籌者的getter方法:

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    // lazy initialization的getter方法
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    // App的目前路徑 / Documents / AppName.sqlite
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataRef.sqlite"];
    
    NSError *error = nil;
    
    // 通過托管對象模型來初始化持久化倉庫統籌者
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    
    // 根據配置選項将storeURL指定的存儲媒體(sqlite,XML,二進制檔案,記憶體等)添加到統籌者的管轄範圍内
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:storeURL
                                                         options:nil
                                                           error:&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. 
         
         Typical reasons for an error here include:
         * The persistent store is not accessible;
         * The schema for the persistent store is incompatible(沖突) with current managed object model.
         Check the error message to determine what the actual problem was.
         
         
         If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
         
         If you encounter schema incompatibility errors during development, you can reduce their frequency by:
         * Simply deleting the existing store:
         [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
         
         * Performing automatic lightweight migration by passing the following dictionary as the options parameter:
         @{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}
         
         Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
         
         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    
    
    return _persistentStoreCoordinator;
}
           

這裡同樣是 lazy initialization的初始化方式。

使用托管對象模型建立統籌者對象後,将持久化倉庫添加到自己的管轄範圍之下,添加的倉庫必須指定資料的儲存方式,如存儲路徑,存儲的類型(SQLite資料庫,XML檔案,二進制檔案,記憶體等)。另外下列方法給出Documents目錄的路徑:

- (NSURL *)applicationDocumentsDirectory {
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
           

托管對象模型的getter方法:

- (NSManagedObjectModel *)managedObjectModel {
    // lazy initialization的getter方法
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    
    // momd檔案由xcdatamodeld生成,用來初始化對象模型對象
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataRef" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}
           

同樣是 lazy initialization的初始化方式。

資料模型根據Bundle中的momd目錄下的檔案建立:

CoreData模闆代碼分析

Bundle中的momd目錄由項目中的xcdatamodeld檔案加載後生成,xcdatamodeld檔案的内容就是一個對象圖,可以讓我們更清晰直覺地設計對象模型。

至此,所有由CoreData自動引入的代碼分析完畢。

事實上,如果不想使用CoreData模闆來導入該架構,那麼肯定要了解上述代碼的作用,才能靈活地在項目中加入CoreData架構。

繼續閱讀