16 CoreData
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

16 CoreData

on

  • 796 views

iOS Core Data:关系对象模型

iOS Core Data:关系对象模型

Statistics

Views

Total Views
796
Views on SlideShare
796
Embed Views
0

Actions

Likes
0
Downloads
7
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

16 CoreData Presentation Transcript

  • 1. Core Data范圣刚,princetoad@gmail.com, www.tfan.org
  • 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。
  • 4. 对 Homepwner 使⽤用Core Data
  • 5. • 我们现在的 Homepwner 应⽤用使⽤用 archiving 来保 存和加载数据,对于数据量较⼩小的情况还可以, 但是对于数据量⾮非常⼤大的情况下,我们可能就想 要能够增量提取和更新数据的 Core Data。
  • 6. • ⾸首先,我们还是要把 Core Data framework 加⼊入我 们的项⺫⽬目。• 选择 Homepwner target,在 Build Phases 下⾯面, 打开 Link Binary with Libraries, 加 + 号 添加 Core Data framework
  • 7. 模型⽂文件
  • 8. Core Data ⾓角⾊色
  • 9. • table/class -> entity• columns/instance -> attributes• BNRItem entity
  • 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 将在运⾏行时创建,⽽而不是从⽂文件保存和加载。
  • 15. 再增加⼀一个⽤用于排序的属性• orderingValue -> Double
  • 16. relationship• ⺫⽬目前模型⽂文件对于保存和加载 items 已经⾜足够了• 我们再增加⼀一个新的名为 BNRAssetType 的实体, ⽤用于描述 items 的分类。• 这样就构造出⼀一个实体间的 relation,演⽰示 Core Data 实体间关系的功能。
  • 17. • 再添加⼀一个名为 BNRAssetType 的实体⽂文件• 增加⼀一个叫做 label 的属性,类型是 String,把它 作为 items 分类的名字
  • 18. • 现在我们需要来建⽴立 BNRAssetType 和 BNRItem 之 间的关系
  • 19. Homepwner 中的实体
  • 20. • 给模型⽂文件增加 relationships。• 选择 BNRAssetType 实体,点击 Relationship 部分 的 + 号。 • 在 Relationship 列把这个 relation 命名为 items; • 然后从 Destination 列中选择 BNRItem; • 在 data model inspector 中,勾选 To-Many Relationship
  • 21. • 给 BNRItem 实体增加⼀一个名为 assetType 的关系, 把 BNRAssetType 作为⺫⽬目的,在 Inverse 列,选择 items。
  • 22. NSManagedObject 及其⼦子类
  • 23. • 当⼀一个对象被提取出来时,默认类型是NSManagedObject,是 NSObject的⼀一个⼦子类,知道如何跟 Core Data 互相操作。• NSManagedObject 类似字典:持有实体中所有属性的⼀一个 key-value pair。• NSManagedObject 差不多就是⼀一个容器。如果我们想让模型对象做更多⼯工作,我们必须⼦子类化NSManagedObject。
  • 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:[itemdateCreated]]; [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];}
  • 32. 更新 BNRItemStore
  • 33. BNRItemStore 和NSManagedObjectContext
  • 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 [documentDirectorystringByAppendingPathComponent:@"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:nilerror:&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;}
  • 39. NSFetchRequest 和 NSPredicate
  • 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 = [NSSortDescriptorsortDescriptorWithKey:@"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: %@", [errorlocalizedDescription]]; } 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];
  • 45. 增加和删除 items
  • 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 = [NSEntityDescriptioninsertNewObjectForEntityForName:@"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];}
  • 48. 重排 items
  • 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);}