iCloud服务
iCloud 是苹果提供的云端服务,用户可以将通讯录、备忘录、邮件、照片、音乐、视频等备份到云服务器并在各个苹果设备间直接进行共享而无需关心数据同步问题,甚至 即使你的设备丢失后在一台新的设备上也可以通过Apple ID登录同步。当然这些内容都是iOS内置的功能,那么对于开放者如何利用iCloud呢?苹果已经将云端存储功能开放给开发者,利用iCloud开发者 可以存储两类数据:用户文档和应用数据、应用配置项。前者主要用于一些用户文档、文件的存储,后者更类似于日常开放中的偏好设置,只是这些配置信息会同步 到云端。
要进行iCloud开发同样需要一些准备工作(下面的准备工作主要是针对真机的,模拟器省略Provisioning Profile配置过程):
1、 2步骤仍然是创建App ID启用iCloud服务、生成对应的配置(Provisioning Profile),这个过程中Bundle ID可以使用通配符(Data Protection、iCloud、Inter-App Audio、Passbook服务在创建App ID时其中的Bundle ID是可以使用通配ID的)。
3.在Xcode中创建项目(假设项目名称为“kctest”)并在项目的 Capabilities中找到iCloud并打开。这里需要注意的就是由于在此应用中要演示文档存储和首选项存储,因此在Service中勾选 “Key-value storae”和“iCloud Documents”:
在项目中会自动生成一个”kctest.entitlements”配置文件,这个文档配置了文档存储容器标识、键值对存储容器标识等信息。
4.无论是真机还是模拟器都必须在iOS“设置”中找到iCloud设置登录账户,注意这个账户不必是沙盒测试用户。
A.首先看一下如何进行文档存储。文档存储主要是使用UIDocument类来完成,这个类提供了新建、修改(其实在API中是覆盖操作)、查询文档、打开文档、删除文档的功能。
UIDocument 对文档的新增、修改、删除、读取全部基于一个云端URL来完成(事实上在开发过程中新增、修改只是一步简单的保存操作),对于开发者而言没有本地和云端之 分,这样大大简化了开发过程。这个URL可以通过NSFileManager的URLForUbiquityContainerIdentifier:方 法获取,identifier是云端存储容器的唯一标识,如果传入nil则代表第一个容器(事实上这个容器可以通过前面生成的 “kctest.entiements”中的Ubiquity Container Identifiers来获取。如上图可以看到这是一个数组,可以配置多个容器,例如我们的第一个容器标识是 “iCloud.$(CFBundleIdentifier)”,其中$(CFBundleIdentifier)是Bundle ID,那么根据应用的Bundle ID就可以得知第一个容器的标识是“iCloud.com.cmjstudio.kctest”。)。下面是常用的文档操作方法:
-(void)saveToURL:forSaveOperation:completionHandler::将指定URL的文档保存到iCloud(可以是新增或者覆盖,通过saveOperation参数设定)。
-(void)openWithCompletionHandler::打开当前文档。
注意:删除一个iCloud文档是使用NSFileManager的removeItemAtURL:error:方法来完成的。
由 于实际开发过程中数据的存储和读取情况是复杂的,因此UIDocument在设计时并没有提供统一的存储方式来保存数据,而是希望开发者自己继承 UIDocument类并重写-(id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError和-(BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError方法来根据不同的文档类型自己来操作数据(contents)。这两个方法分别在保存文档 (-(void)saveToURL:forSaveOperation:completionHandler:)和打开文档 (-(void)openWithCompletionHandler:)时调用。通常在子类中会定义一个属性A来存储文档数据,当保存文档时,会通过 -(id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError方法将A转化为NSData或者NSFileWrapper(UIDocument保存数据的本质就是保存转化得到的NSData或 者NSFileWrapper);当打开文档时,会通过-(BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError方法将云端下载的NSData或者NSFileWrapper数据转化为A对应类型的数据。为了方便演示下面简单定义一个继承自 UIDocument的KCDocument类,在其中定义一个data属性存储数据:
1 //
2 // KCDocument.m
3 // kctest 4 // 5 // Created by Kenshin Cui on 14/4/5. 6 // Copyright (c) 2015年 cmjstudio. All rights reserved. 7 // 8 #import "KCDocument.h" 9 @interface KCDocument() 10 @end 11 @implementation KCDocument 12 #pragma mark - 重写父类方法 13 /** 14 * 保存时调用 15 * 16 * @param typeName 17 * @param outError 18 * 19 * @return 20 */ 21 -(id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError{ 22 if (self.data) { 23 return [self.data copy]; 24 } 25 return [NSData data]; 26 } 27 /** 28 * 读取数据时调用 29 * 30 * @param contents 31 * @param typeName 32 * @param outError 33 * 34 * @return 35 */ 36 -(BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError{ 37 self.data=[contents copy]; 38 return true; 39 } 40 @end
如 果要加载iCloud中的文档列表就需要使用另一个类NSMetadataQuery,通常考虑到网络的原因并不会一次性加载所有数据,而利用 NSMetadataQuery并指定searchScopes为NSMetadataQueryUbiquitousDocumentScope来限制 查找iCloud文档数据。使用NSMetadataQuery还可以通过谓词限制搜索关键字等信息,并在搜索完成之后通过通知的形式通知客户端搜索的情 况。
大家都知道微软的OneNote云笔记本软件,通过它可以实现多种不同设置间的笔记同步,这里就简单实现一个基于iCloud服务的 笔 记软件。在下面的程序中实现笔记的新增、修改、保存、读取等操作。程序界面大致如下,点击界面右上方增加按钮增加一个笔记,点击某个笔记可以查看并编辑。
在主视图控制器首先查询所有iCloud保存的文档并在查询通知中遍历查询结果保存文档名称和创建日期到UITableView展示;其次当用户点击了增加按钮会调用KCDocument完成文档添加并导航到文档详情界面编辑文档内容。
1 //
2 // KCMainTableViewController.m
3 // kctest 4 // 5 // Created by Kenshin Cui on 14/4/5. 6 // Copyright (c) 2015年 cmjstudio. All rights reserved. 7 // 8 #import "KCMainTableViewController.h" 9 #import "KCDocument.h" 10 #import "KCDetailViewController.h" 11 #define kContainerIdentifier @"iCloud.com.cmjstudio.kctest" // 容器id,可以从生产的entitiements文件中查看Ubiquity Container Identifiers(注意其中 的$(CFBundleIdentifier)替换为BundleID) 12 @interface KCMainTableViewController () 13 @property (strong,nonatomic) KCDocument *document;//当前选中的管理对象 14 @property (strong,nonatomic) NSMutableDictionary *files; //现有文件名、创建日期集合 15 @property (strong,nonatomic) NSMetadataQuery *dataQuery;//数据查询对象,用于查询iCloud文档 16 @end 17 @implementation KCMainTableViewController 18 #pragma mark - 控制器视图方法 19 - (void)viewDidLoad { 20 [super viewDidLoad]; 21 22 [self loadDocuments]; 23 } 24 #pragma mark - UI事件 25 //新建文档 26 - (IBAction)addDocumentClick:(UIBarButtonItem *)sender { 27 UIAlertController *promptController=[UIAlertController alertControllerWithTitle:@"KCTest" message:@"请输入笔记名称" preferredStyle:UIAlertControllerStyleAlert]; 28 [promptController addTextFieldWithConfigurationHandler:^(UITextField *textField) { 29 textField.placeholder=@"笔记名称"; 30 }]; 31 UIAlertAction *okAction=[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 32 UITextField *textField= promptController.textFields[0]; 33 [self addDocument:textField.text]; 34 }]; 35 [promptController addAction:okAction]; 36 UIAlertAction *cancelAction=[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { 37 38 }]; 39 [promptController addAction:cancelAction]; 40 [self presentViewController:promptController animated:YES completion:nil]; 41 } 42 #pragma mark - 导航 43 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 44 if ([segue.identifier isEqualToString:@"noteDetail"]) { 45 KCDetailViewController *detailController= segue.destinationViewController; 46 detailController.document=self.document; 47 } 48 } 49 #pragma mark - 属性 50 -(NSMetadataQuery *)dataQuery{ 51 if (!_dataQuery) { 52 //创建一个iCloud查询对象 53 _dataQuery=[[NSMetadataQuery alloc]init]; 54 _dataQuery.searchScopes=@[NSMetadataQueryUbiquitousDocumentsScope]; 55 //注意查询状态是通过通知的形式告诉监听对象的 56 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(metadataQueryFinish:) name:NSMetadataQueryDidFinishGatheringNotification object:_dataQuery];//数据获取完成通知 57 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(metadataQueryFinish:) name:NSMetadataQueryDidUpdateNotification object:_dataQuery];//查询更新通知 58 } 59 return _dataQuery; 60 } 61 #pragma mark - 私有方法 62 /** 63 * 取得云端存储文件的地址 64 * 65 * @param fileName 文件名,如果文件名为nil则重新创建一个url 66 * 67 * @return 文件地址 68 */ 69 -(NSURL *)getUbiquityFileURL:(NSString *)fileName{ 70 //取得云端URL基地址(参数中传入nil则会默认获取第一个容器) 71 NSURL *url= [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:kContainerIdentifier]; 72 //取得Documents目录 73 url=[url URLByAppendingPathComponent:@"Documents"]; 74 //取得最终地址 75 url=[url URLByAppendingPathComponent:fileName]; 76 return url; 77 } 78 /** 79 * 添加文档到iCloud 80 * 81 * @param fileName 文件名称(不包括后缀) 82 */ 83 -(void)addDocument:(NSString *)fileName{ 84 //取得保存URL 85 fileName=[NSString stringWithFormat:@"%@.txt",fileName]; 86 NSURL *url=[self getUbiquityFileURL:fileName]; 87 88 /** 89 创建云端文档操作对象 90 */ 91 KCDocument *document=