Mais conteúdo relacionado
Semelhante a 16 CoreData (20)
16 CoreData
- 2. • 保存和加载数据
• Local or remote?
• Archiving or Core Data?
• Archiving: 对整个⽂文件进⾏行操作
• Core Data: 操作存储对象的⼦子集
• 性能
- 3. 对象-关系映射(ORM)
• Core Data 是提供了 object-relational mapping 的⼀一
个框架,可以把 Objective-C 对象转成存储在
SQLite 数据库⽂文件的数据,反之亦然。
• Core Data 提供了⼀一种可以提取和存储数据到关系
数据库⽽而不需要了解 SQL 的能⼒力。
• 我们这⼀一个章节在 Homepwner 的 BNRItemStore
中⽤用 Core Data 替换掉原来的 keyed archiving。
- 5. • 我们现在的 Homepwner 应⽤用使⽤用 archiving 来保
存和加载数据,对于数据量较⼩小的情况还可以,
但是对于数据量⾮非常⼤大的情况下,我们可能就想
要能够增量提取和更新数据的 Core Data。
- 6. • ⾸首先,我们还是要把 Core Data framework 加⼊入我
们的项⺫⽬目。
• 选择 Homepwner target,在 Build Phases 下⾯面,
打开 Link Binary with Libraries, 加 + 号 添加 Core
Data framework
- 10. • 打开 Homepwner.xcodeproj
• File -> New -> File, iOS -> Core Data, Data Model, 命
名为 Homepwner
• 将会创建⼀一个 Homepwner.xcdatamodeld 的⽂文件
- 11. • 从 project navigator 中打开这个⽂文件,我们就可以
看到可以操作 Core Data model file 的⽤用户界⾯面
- 12. • 找到屏幕左下⾓角的 “Add Entity”按钮点击,⼀一个新
的 Entity 将会出现在左⼿手边的 entities 列表中,命名
为 BNRItem
- 13. 在 Attributes 中对应的设置属性
Attribute type
itemName String
serialNumber String
valueInDollars Integer 32
dateCreated Date
imageKey String
thumbnail Binary Data
thumbnail Undefined
- 14. • 从 Attributes 中选择 thumbnail,点击 inspector 中
的 attribute inspector,勾选 Transient
• 设成 Transient 是告诉 Core Data 我们的 thumbnail
将在运⾏行时创建,⽽而不是从⽂文件保存和加载。
- 20. • 给模型⽂文件增加 relationships。
• 选择 BNRAssetType 实体,点击 Relationship 部分
的 + 号。
• 在 Relationship 列把这个 relation 命名为 items;
• 然后从 Destination 列中选择 BNRItem;
• 在 data model inspector 中,勾选 To-Many Relationship
- 21. • 给 BNRItem 实体增加⼀一个名为 assetType 的关系,
把 BNRAssetType 作为⺫⽬目的,在 Inverse 列,选择
items。
- 24. • 选中 BNRItem 实体,显⽰示 data model inspector,
并更改 Class 字段为 BNRItem。
• 现在当 BNRItem 实体被提取出来时,这个对象的
类型将会是 BNRItem。
- 25. • 在 Finder 中,⾸首先备份好我们的 BNRItem.h 和
BNRItem.m ⽂文件
• 然后在 Xcode 中把这两个⽂文件从 project navigator
中删除。
- 26. • 重新打开 Homepwner.xcdatamodeld
• 选择 BNRItem 实体
• 然后选择从 New 菜单中选择 File,iOS -> Core
Data, 选择 NSManagedObject subclass 选项,提⽰示
保存的时候,勾选“Use scalar properties for
primitive data types”
- 27. • Xcode 将会⽣生成两个新的 BNRItem.h 和 BNRItem.m
⽂文件
• 打开 BNRItem.h, 把 thumbnail 属性的类型改成
UIImage
• 然后增加⼀一个跟之前⼀一样的⼀一个⽅方法声明
@interface BNRItem : NSManagedObject
@property (nonatomic) int32_t itemName;
@property (nonatomic, retain) NSString * serialNumber;
@property (nonatomic) int32_t valueInDollars;
@property (nonatomic) NSTimeInterval dateCreated;
@property (nonatomic, retain) NSString * imageKey;
@property (nonatomic, retain) NSData * thumbnailData;
//@property UNKNOWN_TYPE UNKNOWN_TYPE thumbnail;
@property (nonatomic, strong)UIImage *thumbnail;
@property (nonatomic) double orderingValue;
@property (nonatomic, retain) NSManagedObject *assetType;
- (void)setThumbnailDataFromImage:(UIImage *)image;
@end
- 28. • NSDate 变成了 NSTimeInterval
• 打开我们的 DetailViewController.h, 定位到
viewWillAppear:, 替换下⾯面的代码
// [dateLabel setText:[dateFormatter stringFromDate:[item dateCreated]]];
// 把 time interval 转换成 NSDate
NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:[item
dateCreated]];
[dateLabel setText:[dateFormatter stringFromDate:date]];
- 29. • 然后,从旧的 BNRItem.m 中拷⻉贝
setThumbNailFromImage: ⽅方法到新的⽂文件
- (void)setThumbnailDataFromImage:(UIImage *)image
{
CGSize origImageSize = [image size];
// thumnail 的矩形⼤大⼩小
CGRect newRect = CGRectMake(0, 0, 40, 40);
// 计算缩放⽐比
float ratio = MAX(newRect.size.width / origImageSize.width, newRect.size.height / origImageSize.height);
// ⽣生成⼀一个带缩放因⼦子的透明位图上下⽂文
UIGraphicsBeginImageContextWithOptions(newRect.size, NO, 0.0);
// ⽣生成⼀一个圆⾓角矩形路径
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:newRect cornerRadius:5.0];
// 后续绘制 clip 到这个圆⾓角矩形
[path addClip];
// 图⽚片放到缩略图中间
CGRect projectRect;
projectRect.size.width = ratio * origImageSize.width;
projectRect.size.height = ratio * origImageSize.height;
projectRect.origin.x = (newRect.size.width - projectRect.size.width) / 2.0;
projectRect.origin.y = (newRect.size.height - projectRect.size.height) / 2.0;
// 把图⽚片绘制上来
[image drawInRect:projectRect];
// 从图⽚片上下⽂文获得图⽚片,作为我们的缩略图保存
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
[self setThumbnail:smallImage];
// 得到该图⽚片 PNG 形式,把它作为我们可以 archive 的数据
NSData *data = UIImagePNGRepresentation(smallImage);
[self setThumbnailData:data];
// 完成以后,清除图⽚片上下⽂文资源
UIGraphicsEndImageContext();
}
- 30. • 在 BNRItem.m 中重写 awakeFromFetch ⽅方法,来
从 thumbnailData 设置 thumbnail
• 使⽤用 archiving 的时候我们是在 initWithCoder: 时
做的
- (void)awakeFromFetch
{
[super awakeFromFetch];
UIImage *tn = [UIImage imageWithData:[self thumbnailData]];
[self setPrimitiveValue:tn forKey:@"thumbnail"];
}
- 31. • 我们创建⼀一个新的 BNRItem 实例时,它将会被加
⼊入数据库。
• 当对象被添加到数据库时,它将被发送
awakeInsert 消息
• 在 BNRItem.m 中实现 awakeFromInsert
• 原先我们是在 BNRItem 的 designated initializer 中
添加⼀一些附加⾏行为的
- (void)awakeFromInsert
{
[super awakeFromInsert];
NSTimeInterval t = [[NSDate date] timeIntervalSinceReferenceDate];
[self setDateCreated:t];
}
- 34. • 在 BNRItemStore.h 中,导⼊入 Core Data 然后增加三
个实例变量
#import <Foundation/Foundation.h>
// 导⼊入 Core Data 头⽂文件
#import <CoreData/CoreData.h>
@class BNRItem;
@interface BNRItemStore : NSObject
{
NSMutableArray *allItems;
NSMutableArray *allAssetTypes;
NSManagedObjectContext *context;
NSManagedObjectModel *model;
}
- 35. • 在BNRItemStore.m 中,更改 itemArchivePath 的实
现来返回⼀一个不同的路径供 Core Data 存储数据
- (NSString *)itemArchivePath
{
NSArray *documentDirectories =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,
YES);
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
// return [documentDirectory
stringByAppendingPathComponent:@"items.archive"];
return [documentDirectory stringByAppendingPathComponent:@"store.data"];
}
- 36. • BNRItemStore 被初始化时,它需要设置
NSManagedObjectContext 和
NSPersistentStoreCoordinator
• 我们需要创建⼀一个 NSManagedObjectModel 来持
有 Homepwner.xcdatamodeld 的实体信息,并且
使⽤用这个对象初始化 persistent store coordinator
• 因此我们将创建 NSManagedObjectContext 的实
例,并且指定它使⽤用这个 persistent store
coordinator 来保存和加载对象
- 37. • 在 BNRItemStore.m 中更新 init
- (id)init
{
self = [super init];
if (self) {
//// allItems = [[NSMutableArray alloc] init];
// NSString *path = [self itemArchivePath];
// allItems = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
// // 如果数组之前没有被保存,创建⼀一个新的空数组
// if (!allItems) {
// allItems = [[NSMutableArray alloc] init];
// }
// 读取 Homepwner.xcdatamodeld
model = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:model];
// SQLite ⽂文件在哪⼉儿?
NSString *path = [self itemArchivePath];
NSURL *storeURL = [NSURL fileURLWithPath:path];
NSError *error = nil;
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil
error:&error]) {
[NSException raise:@"Open failed" format:@"Reason: %@", [error localizedDescription]];
}
// ⽣生成 managed object context
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:psc];
// 不需要管理 undo
[context setUndoManager:nil];
}
return self;
}
- 38. • 之前我们使⽤用 keyed archiving 的时候,当我们要
求保存数据,BNRItemStore 将会写⼊入整个
NSMutableArray 的 BNRItems。
• 现在我们让它给 NSManagedObjectContext 发送
save: 消息。Context 将会更新 store.data 中从最后
⼀一次保存其有任何变更的所有记录。
• 在 BNRItemStore.m 中,更改 saveChanges
@implementation BNRItemStore
- (BOOL)saveChanges
{
// // 返回成功或失败
// NSString *path = [self itemArchivePath];
// return [NSKeyedArchiver archiveRootObject:allItems toFile:path];
NSError *err = nil;
BOOL successful = [context save:&err];
if (!successful) {
NSLog(@"Error saving: %@", [err localizedDescription]);
}
return successful;
}
- 40. • 要从 NSManagedObjectContext 提取数据,我们需
要 prepare and execute ⼀一个 NSFetchRequest。
• 当这个 fetch 请求被执⾏行以后,我们将得到⼀一个符
合这个请求的 parameters 的所有对象的⼀一个数组
• fetch 请求需要我们想要从中获取对象的⼀一个实体
的描述。这⾥里我们指定 BNRItem 实体。
- 41. • 也可以设置请求的 sort descriptors 来指定数组中
对象的顺序。
• ⼀一个 sort descriptor 具有⼀一个映射到⼀一个 attribute
的 key,和⼀一个表⽰示正序还是倒序的 BOOL 值
• 我们希望使⽤用使⽤用 orderingValue 正序排列返回的
BNRItem
• 在 BNRItemStore.h 中,声明⼀一个新的⽅方法
- (void)loadAllItems;
- 42. • 在 BNRItemStore.m 中定义 loadAllItems 来 prepare
and execute ⼀一个 fetch 请求,并且保存结果到
allItems 数组
- (void)loadAllItems
{
if (!allItems) {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *e = [[model entitiesByName]
objectForKey:@"BNRItem"];
[request setEntity:e];
NSSortDescriptor *sd = [NSSortDescriptor
sortDescriptorWithKey:@"orderingValue" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sd]];
NSError *error;
NSArray *result = [context executeFetchRequest:request error:&error];
if (!result) {
[NSException raise:@"Fetch failed" format:@"Reason: %@", [error
localizedDescription]];
}
allItems = [[NSMutableArray alloc] initWithArray:result];
}
}
- 43. • 在 BNRItemStore.m 中,在 init 的末尾发送这个消
息给 BNRItemStore
// 不需要管理 undo
[context setUndoManager:nil];
[self loadAllItems];
}
return self;
}
- 44. • 要想有选择性的 fetch ⼀一些实例,我们可以添加⼀一
个 predicate(⼀一个 NSPredicate)到我们的 fetch 请
求,只有满⾜足这个 predicate 的对象会被返回。
• predicate 包含的是⼀一个可以为 true 或 false 的条
件。例如:
// ⼀一个 predicate 的例⼦子,设定 fetch 条件
NSPredicate *p = [NSPredicate predicateWithFormat:@"valueInDollars > 50"];
[request setPredicate:p];
• predicate 也可以被⽤用来过滤⼀一个数组的内容,例如
// predicate ⽤用来过滤数组的例⼦子
NSArray *expensiveStuff = [allItems filteredArrayUsingPredicate:p];
- 46. • 要创建⼀一个新的 BNRItem,我们将请求
NSManagedObjectContex 从 BNRItem 实体插⼊入⼀一
个新的对象。它将返回⼀一个 BNRItem 的实例。
• 在 BNRItemStore.m 中,编辑 createItem ⽅方法
- (BNRItem *)createItem
{
//// BNRItem *p = [BNRItem randomItem];
// BNRItem *p = [[BNRItem alloc] init];
double order;
if ([allItems count] == 0) {
order = 1.0;
} else {
order = [[allItems lastObject] orderingValue] + 1.0;
}
NSLog(@"在 %d 个 items 之后添加,顺序是:%.2f", [allItems count], order);
BNRItem *p = [NSEntityDescription
insertNewObjectForEntityForName:@"BNRItem" inManagedObjectContext:context];
[p setOrderingValue:order];
[allItems addObject:p];
return p;
}
- 47. • 当⼀一个⽤用户删除了⼀一个 item 的时候,我们必须通
知 context,这样它也会被从数据库删除。
• 在 BNRItem.m 中,增加下列的代码到 removeItem:
- (void)removeItem:(BNRItem *)p
{
// BNRItem 被从 store 中移除的时候,它的 image 也应该被从⽂文件系统删除
NSString *key = [p imageKey];
[[BNRImageStore sharedStore] deleteImageForkey:key];
// 增加通知 context 有数据被删除的代码
[context deleteObject:p];
[allItems removeObjectIdenticalTo:p];
}
- 49. • 我们需要为 BNRItem 替换的最后⼀一点功能是在
BNRItemStore 中重新排序 BNRItems。
• 因为 Core Data 不会⾃自动化的处理排序,每次它在
table view 中被移动时我们必须更新 BNRItem 的
orderingValue。
• 在 BNRItemStore.m 中,修改
moveItemAtIndex:toIndex: 来处理重新排序 items
- 50. - (void)moveItemAtIndex:(int)from toIndex:(int)to
{
if (from == to) {
return;
}
// 得到被移动的对象的指针,以便我们可以把它重新插⼊入
BNRItem *p = [allItems objectAtIndex:from];
// 从数组中删除
[allItems removeObjectAtIndex:from];
// 在新的位置重新插⼊入
[allItems insertObject:p atIndex:to];
// Core Data 的排序
// 为被移动的对象计算⼀一个新的 orderValue
double lowerBound = 0.0;
// 数组中在它之前是否有⼀一个对象?
if (to > 0) {
lowerBound = [[allItems objectAtIndex:to -1] orderingValue];
} else {
lowerBound = [[allItems objectAtIndex:1] orderingValue] - 2.0;
}
double upperBound = 0.0;
// 数组中在它之后是否有⼀一个对象
if (to < [allItems count] - 1) {
upperBound = [[allItems objectAtIndex:to + 1] orderingValue];
} else {
upperBound = [[allItems objectAtIndex:to -1] orderingValue] + 2.0;
}
double newOrderValue = (lowerBound + upperBound) / 2.0;
NSLog(@"moving to order: %f", newOrderValue);
}