iOS開發基礎135-Core Data

Mr.陳發表於2024-07-22

Objective-C (OC) 中使用 Core Data 是iOS應用開發中管理模型層物件的一種有效工具。Core Data 使用 ORM (物件關係對映) 技術來抽象化和管理資料。這不僅可以節省時間,還能減少程式設計錯誤。以下是使用 Core Data 的詳細介紹,包括示例程式碼,以及深入底層的一些分析。

基本概念

  1. 持久化容器 (NSPersistentContainer): iOS 10 引入的,封裝了 Core Data 棧的設定,包括託管物件模型 (NSManagedObjectModel),持久化儲存協調器 (NSPersistentStoreCoordinator),和上下文 (NSManagedObjectContext)。

  2. 託管物件模型 (NSManagedObjectModel): 描述應用的資料模型,包括實體(Entity)和這些實體之間的關係。

  3. 持久化儲存協調器 (NSPersistentStoreCoordinator): 負責協調託管物件上下文和持久化儲存。

  4. 上下文 (NSManagedObjectContext): 用於在記憶體中管理物件。執行建立、讀取、更新、刪除操作時,這些更改暫時只發生在上下文中,直到儲存更改到持久層。

使用示例

以下是一個簡單的使用 Core Data 建立和查詢物件的示例:

步驟 1: 配置資料模型

首先,透過 Xcode 的 Data Model Editor 建立資料模型檔案(.xcdatamodeld)。假設定義了一個 Person 實體,有 nameage 兩個屬性。

步驟 2: 設定持久化容器

在 AppDelegate 中設定持久化容器:

#import <CoreData/CoreData.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (readonly, strong) NSPersistentContainer *persistentContainer;

- (void)saveContext;

@end

@implementation AppDelegate

@synthesize persistentContainer = _persistentContainer;

// 懶載入 persistentContainer
- (NSPersistentContainer *)persistentContainer {
    // 如果容器已經被初始化了,直接返回
    if (_persistentContainer != nil) {
        return _persistentContainer;
    }
    
    // 使用名為 MyModel 的模型檔案建立容器
    _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"MyModel"];
    [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
        if (error != nil) {
            // 錯誤處理,實際應用中應該替換為更合適的錯誤處理
            NSLog(@"Unresolved error %@, %@", error, error.userInfo);
            abort();
        }
    }];
    return _persistentContainer;
}
@end

步驟 3: 使用 Core Data 新增和查詢

在合適的地方(如 ViewController)進行資料的新增和查詢:

#import "AppDelegate.h"
#import <CoreData/CoreData.h>

- (void)insertNewPersonWithName:(NSString *)name age:(int)age {
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    NSManagedObjectContext *context = appDelegate.persistentContainer.viewContext;
    
    // 建立新的 Person 實體物件
    NSManagedObject *newPerson = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
    [newPerson setValue:name forKey:@"name"];
    [newPerson setValue:@(age) forKey:@"age"];
    
    NSError *error = nil;
    // 儲存到持久層
    if (![context save:&error]) {
        NSLog(@"儲存失敗: %@, %@", error, error.userInfo);
    }
}

- (NSArray *)fetchPersons {
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    NSManagedObjectContext *context = appDelegate.persistentContainer.viewContext;
    
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
    
    NSError *error = nil;
    NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
    if (!results) {
        NSLog(@"查詢失敗: %@, %@", error, error.userInfo);
    }
    return results;
}

深入分析

Core Data 的底層使用了 SQLite 作為預設的持久化方式(儘管你可以選擇記憶體或者自定義解決方案),但開發者無需直接與資料庫互動,所有的操作都是透過上述的物件和 API 完成。Core Data 框架負責轉換這些操作為 SQLite 命令並執行。

Core Data 效能最佳化

  • 批次請求: iOS 8 引入了批次刪除和更新,這樣可以在不載入資料到記憶體的情況下直接在持久層執行操作,極大提升效率。

  • 預獲取: 對於頻繁訪問的關聯物件,可以使用預獲取來減少查詢次數。

  • 輕量級遷移: 對於資料模型的更改,透過輕量級遷移避免手動處理資料結構變動。

封裝

對於Core Data的使用,進行二次封裝可以提高程式碼的複用性,讓外部呼叫變得更加簡潔。我們可以建立一個單例類CoreDataManager來管理Core Data的常見操作,比如增刪改查。

首先,你需要確保你的資料模型(.xcdatamodeld檔案)已經設定好,舉個例子,這裡假設我們有一個Person的Entity,它有兩個屬性:name(String型別)和age(Int16型別)。

步驟 1: 建立Core Data管理類

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface CoreDataManager : NSObject

@property (readonly, strong) NSPersistentContainer *persistentContainer;
+ (instancetype)sharedManager;
- (void)saveContext;
- (void)insertPersonWithName:(NSString *)name age:(NSNumber *)age completion:(void(^)(BOOL success, NSError *error))completion;
- (void)fetchAllPersons:(void(^)(NSArray *persons, NSError *error))completion;

@end

@implementation CoreDataManager

+ (instancetype)sharedManager {
    static CoreDataManager *sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManager = [[self alloc] init];
    });
    return sharedManager;
}

- (NSPersistentContainer *)persistentContainer {
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"YourModelName"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }
    return _persistentContainer;
}

- (void)saveContext {
    NSManagedObjectContext *context = self.persistentContainer.viewContext;
    NSError *error = nil;
    if ([context hasChanges] && ![context save:&error]) {
        NSLog(@"Unresolved error %@, %@", error, error.userInfo);
        abort();
    }
}

- (void)insertPersonWithName:(NSString *)name age:(NSNumber *)age completion:(void(^)(BOOL success, NSError *error))completion {
    NSManagedObjectContext *context = self.persistentContainer.viewContext;
    NSManagedObject *newPerson = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
    [newPerson setValue:name forKey:@"name"];
    [newPerson setValue:age forKey:@"age"];
    
    NSError *error = nil;
    if (![context save:&error]) {
        NSLog(@"Error saving context: %@, %@", error, error.userInfo);
        completion(NO, error);
    } else {
        completion(YES, nil);
    }
}

- (void)fetchAllPersons:(void(^)(NSArray *persons, NSError *error))completion {
    NSManagedObjectContext *context = self.persistentContainer.viewContext;
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
    
    NSError *error = nil;
    NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
    if (error) {
        NSLog(@"Failed to fetch persons: %@, %@", error, error.userInfo);
        completion(nil, error);
    } else {
        completion(results, nil);
    }
}

@end

使用封裝的CoreDataManager

這裡展示如何使用CoreDataManager進行資料操作:

// 插入新的Person物件
[[CoreDataManager sharedManager] insertPersonWithName:@"John Doe" age:@25 completion:^(BOOL success, NSError *error) {
    if (success) {
        NSLog(@"Person added successfully");
    } else {
        NSLog(@"Failed to add person: %@", error.localizedDescription);
    }
}];

// 獲取所有的Person物件
[[CoreDataManager sharedManager] fetchAllPersons:^(NSArray * _Nonnull persons, NSError * _Nonnull error) {
    if (error) {
        NSLog(@"Failed to fetch persons: %@", error.localizedDescription);
    } else {
        for (NSManagedObject *person in persons) {
            NSString *name = [person valueForKey:@"name"];
            NSNumber *age = [person valueForKey:@"age"];
            NSLog(@"Fetched person: %@, age: %@", name, age);
        }
    }
}];

透過上面的封裝,我們只需呼叫簡單的方法就可以完成對Person物件的增刪改查操作,而不用關心Core Data的具體實現細節。這大大提高了程式碼的可讀性和可維護性。

總結

Core Data 是一個功能強大的框架,透過封裝複雜的底層細節,使得資料管理變得更加簡單。高效地使用 Core Data 必須理解其背後的原理,並遵循最佳實踐來設計應用。

相關文章