在 Objective-C 開發中,資料持久化是核心需求。依據資料量、結構複雜度、查詢需求、安全性,主要有四大方案:
NSArray/NSDictionary/NSUserDefaults) — 適合簡單設定、小量資料| 特性 | 歸檔 | 屬性列表 | SQLite | Core Data |
|---|---|---|---|---|
| 資料量適用 | 小 (<1MB) | 極小 (設定檔級) | 大 (GB 級) | 中大型 |
| 資料結構 | 任意物件圖 | 基本集合類型 | 關聯表格 | 管理物件模型 |
| 查詢能力 | 無 (全讀全寫) | 無 | SQL 全功能 | NSPredicate 豐富 |
| 加密性 | 二進位 (非明文) | 明文 (XML/二進位) | 可加密 (SQLCipher) | 可加密 |
| 效能 | 序列化整體 | 快 (小資料) | 極高 (索引) | 優化良好 |
| 適用場景 | 文檔型、快照、快取 | 用戶偏好、設定 | 大量結構化資料 | 複雜業務模型 |
NSData 寫入檔案NSData 還原物件圖NSCoding (encodeWithCoder:、initWithCoder:)NSString、NSArray、NSDictionary、NSDate 等) 已實作 NSCoding@autoreleasepool {
NSString *homeDir = NSHomeDirectory();
NSArray *array = @[@"one", @"two", @"three"];
NSString *filePath = [homeDir stringByAppendingPathComponent:@"Desktop/test.archive"];
// 歸檔
if (![NSKeyedArchiver archiveRootObject:array toFile:filePath]) {
NSLog(@"歸檔失敗");
} else {
// 解歸檔
NSArray *data = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"%@", data); // 輸出: (one, two, three)
}
NSLog(@"Hello, World!");
}
適用於需手動控制編碼流程、分批寫入、或加入中繼資料的場景:
NSString *homeDir = NSHomeDirectory();
NSString *filePath = [homeDir stringByAppendingPathComponent:@"Desktop/usertest.archive"];
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
NSArray *nameArray = @[@"andy", @"yang"];
[archiver encodeInt:100 forKey:@"age"];
[archiver encodeObject:nameArray forKey:@"names"];
[archiver finishEncoding];
[archiver release];
// 寫入檔案
if ([data writeToFile:filePath atomically:YES]) {
// 解歸檔
NSData *data2 = [NSData dataWithContentsOfFile:filePath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data2];
int age = [unarchiver decodeIntForKey:@"age"];
NSArray *array2 = [unarchiver decodeObjectForKey:@"names"];
NSLog(@"%d", age); // 100
NSLog(@"%@", array2); // (andy, yang)
[unarchiver release];
} else {
NSLog(@"write to file wrong");
}
// Person.h
@interface Person : NSObject <NSCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
// Person.m
@implementation Person
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.name forKey:@"name"];
[coder encodeInteger:self.age forKey:@"age"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
_name = [coder decodeObjectForKey:@"name"];
_age = [coder decodeIntegerForKey:@"age"];
}
return self;
}
@end
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // 寫入 [defaults setObject:@"dark" forKey:@"theme"]; [defaults setBool:YES forKey:@"notificationsEnabled"]; [defaults synchronize]; // 同步到磁碟 (iOS 8+ 可省略) // 讀取 NSString *theme = [defaults stringForKey:@"theme"]; BOOL enabled = [defaults boolForKey:@"notificationsEnabled"];
NSDictionary *config = @{
@"server": @"api.example.com",
@"port": @(443),
@"enabled": @YES
};
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject
stringByAppendingPathComponent:@"config.plist"];
[config writeToFile:path atomically:YES]; // 自動產生 XML plist
NSFetchedResultsController 高效驅動 TableView// AppDelegate.m
@property (readonly, strong) NSPersistentContainer *persistentContainer;
- (NSPersistentContainer *)persistentContainer {
if (!_persistentContainer) {
_persistentContainer = [[NSPersistentContainer alloc] initWithName:@"MyModel"];
[_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *desc, NSError *error) {
if (error) NSLog(@"Core Data load error: %@", error);
}];
}
return _persistentContainer;
}
// 使用範例
NSManagedObjectContext *ctx = self.persistentContainer.viewContext;
Person *p = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:ctx];
p.name = @"John";
p.age = @30;
[ctx save:nil];
NSFileProtectionComplete、DataProtection entitlement沒有銀彈,只有適合場景的選擇。理解各方案的資料模型、查詢能力、效能特性、安全性,才能在架構初期做對決策,避免後期痛苦遷移。建議:小專案從 NSUserDefaults + 歸檔起步,業務增長再引入 Core Data 或 SQLite。