iOS开发之iCloud开发(数据与文档的读写删除)

iCloud是IOS系统提供的默认的云存储,使用APPLE ID登录,如果是普通的app或者游戏的话,一般数据都是存储在自己的服务器,使用自己的帐号密码,所以如果是自己开发的单机app,没有服务器,倒是采用这个存储,可以免去再去注册其他云服务帐号的过程,并且不需要再去开发服务器,与IOS系统结合的也不错。

iCloud可以存储字符串,文档数据和数据库,但是官方不推荐存储Sqlite数据库,如果需要,可以采用core data,但是百度了下,好像iCloud+coreData的方式存储会有挺多坑,所以我也没尝试了。网上详细解释的教程有很多,可以看下我文章最后的参考文章,都是写的比较详细的解释,可以拜读一下,我这个文章主要是怎么使用这个东西的总结,旨在快速的使用集成,所以去掉了项目界面的搭建,最基础的使用。

Simulator Screen Shot 2016年11月2日 下午6.00.34.png

一、项目工程开启iCloud

首先在项目设置》Capabilities中开启iCloud选项,会自动创建一个.entitlements的文件,如果有报错,请参考这个文章:《Add iCloud Containers to your App ID》,如果整个设置都不会,可以参考这个文章《iOS项目iCloud及CloudKit Dashboard运用

开启之后,是这个样子,真机调试和模拟器调试记得要在手机的设置里面登录自己的icloud帐号

屏幕快照 2016-11-03 下午3.21.10.png

二、字符串等数据的读写

2.1、初始化

字符串操作是用的这个类NSUbiquitousKeyValueStore来操作,类似于NSUserDefault,通过

self.myKeyValue = [NSUbiquitousKeyValueStore defaultStore];

实例化

2.2、保存数据

[self.myKeyValue setObject:@"dong" forKey:@"name"];
[self.myKeyValue synchronize];

2.3、读取数据

[self.myKeyValue objectForKey:@"name"]

2.4、消息通知

数据操作的话直接使用上面的操作即可,但是它还有一个通知NSUbiquitousKeyValueStoreDidChangeExternallyNotification,这个通知是在首次使用软件同步的时候,或者其他设备也通过icloud修改数据的时候调用的,可以通过该通知处理相应的逻辑

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(StringChange:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:self.myKeyValue];

字符串读写速度特别快,感觉不出来区别,验证方法很简单,直接设置一个字符串,然后把软件卸载了,之后重新安装之后,直接来获取这个字符串即可,因为是在icloud保存的,所以即使软件卸载了,照样可以获取到之前保存的字符串。

三、文档的读写

再强调一遍,官方不推荐使用iCloud存储Sqlite数据库。

文档的读写比较麻烦点,文件的操作是通过NSFileManager来实现,文件内容的操作是通过UIDocument来实现,文件内容的查询是通过NSMetadataQuery来实现,所以要记住这三个类的作用。

3.1、初始化

3.1.1、NSFileManager初始化

[NSFileManager defaultManager]

3.1.2、UIDocument初始化

UIDocument是一个操作iCloud的基类,因为文件内容的操作需求比较多,所以需要创建一个子类比如MyDocument来继承UIDocument,并且重写下面这两个函数

- (BOOL)loadFromContents:(id)contents ofType:(nullable NSString *)typeName error:(NSError **)outError __TVOS_PROHIBITED
- (nullable id)contentsForType:(NSString *)typeName error:(NSError **)outError __TVOS_PROHIBITED

通过这两个函数可以修改你自己需要保存的本地数据,比如NSData * myData;

#import "MyDocument.h"
@implementation MyDocument

//读取icloud数据调用,响应openWithCompletionHandler
- (BOOL)loadFromContents:(id)contents ofType:(nullable NSString *)typeName error:(NSError **)outError __TVOS_PROHIBITED
{
    self.myData = [contents copy];
    return true;
}

//保存数据、修改数据到icloud,响应save
- (nullable id)contentsForType:(NSString *)typeName error:(NSError **)outError __TVOS_PROHIBITED
{
    if (!self.myData) {
        self.myData = [[NSData alloc] init];
    }
    return self.myData;
}
@end

所以UIDocument的初始化其实是用MyDocument初始化。

UIDocument必须要用下面这个函数通过URL初始化

- (instancetype)initWithFileURL:(NSURL *)url

这个URL就是想要修改的文件在icloud的URL地址,获取地址方法通过你在那个entitlements文件中设置的Ubiquity Container Identifiers来决定的,一般都是"iCloud.软件的bundle id"

#define UbiquityContainerIdentifier @"iCloud.com.damon.iosIcloudDemo"
//获取url
-(NSURL*)getUbiquityContainerUrl:(NSString*)fileName{
    if (!self.myUrl) {
        self.myUrl = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:UbiquityContainerIdentifier];
        if (!self.myUrl) {
            NSLog(@"未开启iCloud功能");
            return nil;
        }
    }
    NSURL *url = [self.myUrl URLByAppendingPathComponent:@"Documents"];
    url = [url URLByAppendingPathComponent:fileName];
    return url;
}

因为每次操作都通过URLForUbiquityContainerIdentifier:来获取URL的话,有可能会出现网络过慢卡死的情况,所以可以使用多线程开辟一个新线程去获取URL,或者把获取到的URL保存下也可以

这样如果你想操作"test.txt"这个文件的话,MyDocument的初始化方法就是下面这样

MyDocument *doc = [[MyDocument alloc] initWithFileURL:[self getUbiquityContainerUrl:@"test.txt"]];

3.1.3、NSMetadataQuery初始化

self.myMetadataQuery = [[NSMetadataQuery alloc] init];

3.2、文档的上传

//创建文档并上传
-(void)uploadDoc{
    NSLog(@"uploadDoc");
    //文档名字
    NSString *fileName [email protected]"test.txt";
    NSURL *url = [self getUbiquityContainerUrl:fileName];
    MyDocument *doc = [[MyDocument alloc] initWithFileURL:url];
    //文档内容
    NSString*str = @"测试文本数据";
    doc.myData = [str dataUsingEncoding:NSUTF8StringEncoding];
    [doc saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
        if (success) {
            NSLog(@"创建成功");
        }
        else{
            NSLog(@"创建失败");
        }
    }];
}

文档的上传就是先通过文件名获得或者创建一个文档的URL地址,然后实例化一个MyDocument,之后把需要写到文档的数据赋值给MyDocument中的myData,通过saveToURL:中的这个UIDocumentSaveForCreating保存类型去创建一个新文档

调用完这个函数的时候,MyDocument里面的继承的下面这个函数

- (nullable id)contentsForType:(NSString *)typeName error:(NSError **)outError
{
    if (!self.myData) {
        self.myData = [[NSData alloc] init];
    }
    return self.myData;
}

这个函数会自动调用,就是把创建的myData保存到了iCloud的文档

3.3、文档的修改

文档的修改和保存一模一样,只是把saveToURL:中的那个SaveOperation保存选项修改成了UIDocumentSaveForOverwriting,其他都没有修改

//保存文档,只是save参数不一样用UIDocumentSaveForOverwriting
-(void)editDoc{
    NSLog(@"editDoc");
    //文档名字
    NSString *fileName [email protected]"test.txt";
    NSURL *url = [self getUbiquityContainerUrl:fileName];
    MyDocument *doc = [[MyDocument alloc] initWithFileURL:url];
    //文档内容
    NSString*str = @"修改了数据";
    doc.myData = [str dataUsingEncoding:NSUTF8StringEncoding];
    [doc saveToURL:url forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
        if (success) {
            NSLog(@"修改成功");
        }
        else{
            NSLog(@"修改失败");
        }
    }];
}

3.4、文档的读取

文档的读取先通过NSMetadataQuery的对象索引iCloud可以获取的的文档

首先注册这两个通知,这两个通知是当索引完成的时候调用和文档有更新的时候调用

//数据获取完成
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(MetadataQueryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:self.myMetadataQuery];
//数据更新通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(MetadataQueryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:self.myMetadataQuery];

然后开始索引iCloud文件

[self.myMetadataQuery setSearchScopes:@[NSMetadataQueryUbiquitousDocumentsScope]];
[self.myMetadataQuery startQuery];

索引完成之后,会调用通知函数

//获取成功
-(void)MetadataQueryDidFinishGathering:(NSNotification*)noti{
    NSLog(@"MetadataQueryDidFinishGathering");
    NSArray *items = self.myMetadataQuery.results;//查询结果集
    //便利结果
    [items enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSMetadataItem*item =obj;
        //获取文件名
        NSString *fileName = [item valueForAttribute:NSMetadataItemFSNameKey];
        //获取文件创建日期
        NSDate *date = [item valueForAttribute:NSMetadataItemFSContentChangeDateKey];
        NSLog(@"%@,%@",fileName,date);
        //读取文件内容
        MyDocument *doc =[[MyDocument alloc] initWithFileURL:[self getUbiquityContainerUrl:fileName]];
        [doc openWithCompletionHandler:^(BOOL success) {
            if (success) {
                NSLog(@"读取数据成功.");
                NSString *dataText = [[NSString alloc] initWithData:doc.myData encoding:NSUTF8StringEncoding];
                NSLog(@"数据:%@",dataText);
            }
        }];
    }];
}

得到的results就是遍历到的结果,然后循环遍历数组,即可得到每一条数据,然后通过MyDocument的myData来获取文档的数据做其他操作即可。

openWithCompletionHandler这个函数调用之后,会调用MyDocument继承的那个函数

//读取icloud数据调用,响应openWithCompletionHandler
- (BOOL)loadFromContents:(id)contents ofType:(nullable NSString *)typeName error:(NSError **)outError __TVOS_PROHIBITED
{
    self.myData = [contents copy];
    return true;
}

这个函数就是把icloud得到的数据返回到本地来。

而下面这个数据更新的通知调用的函数在得到文档数据、修改文档数据时都会调用

//数据有更新
-(void)MetadataQueryDidUpdate:(NSNotification*)noti{
    NSLog(@"icloud数据有更新");
}

3.5、文档删除

删除就是通过NSFileManager来操作的,也是先获取文档在iCloud的URL地址,通过removeItemAtURL删除即可

-(void)removeDoc{
    NSLog(@"removeDoc");
    NSString *fileName [email protected]"test.txt";
    NSURL *url = [self getUbiquityContainerUrl:fileName];
    NSError* error;
    [[NSFileManager defaultManager] removeItemAtURL:url error:&error];
    if (error) {
        NSLog(@"%@",error.localizedDescription);
    }
}

3.6、文档使用总结

当需要获取文档的地址和删除文档的时候,进行文件的操作才会使用NSFileManager这个类

//获取文档URL
[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:UbiquityContainerIdentifier]
//删除文档
[[NSFileManager defaultManager] removeItemAtURL:url error:&error];

当需要下载文档,进行索引,和获取索引结果的时候,会用到NSMetadataQuery这个类

//设置搜索文档
[self.myMetadataQuery setSearchScopes:@[NSMetadataQueryUbiquitousDocumentsScope]];
[self.myMetadataQuery startQuery];
//查询到的结果集合
NSArray *items = self.myMetadataQuery.results;

当该文档需要进行数据的改写读取的时候,使用的则是UIDocument这个类

//保存修改文档内容
MyDocument *doc = [[MyDocument alloc] initWithFileURL:url];
//文档内容
NSString*str = @"测试文本数据";
doc.myData = [str dataUsingEncoding:NSUTF8StringEncoding];
[doc saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {}];
//获取文档内容
 [doc openWithCompletionHandler:^(BOOL success) {
      if (success) {
        NSLog(@"读取数据成功.");
        NSString *dataText = [[NSString alloc] initWithData:doc.myData encoding:NSUTF8StringEncoding];
        NSLog(@"数据:%@",dataText);
       }
  }];

文档的操作完成了,如果非要想去保存sqlite数据,可以参考下stackOverFlow里面的这几个文章

四、结果验证

4.1、首次创建

屏幕快照 2016-11-02 下午5.58.42.png

4.2、修改数据

屏幕快照 2016-11-02 下午5.59.21.png

4.3、切换新帐号

屏幕快照 2016-11-02 下午6.08.47.png

4.4、切换回原帐号

屏幕快照 2016-11-02 下午6.11.44.png

五、Demo下载

GitHub下载:https://github.com/DamonHu/IOSicloudDemo

GitOsc下载:http://git.oschina.net/DamonHoo/IOSicloudDemo

六、其他说明

这个项目感觉用最简洁的方案去说明iCloud的使用,为了说明最基础的应用,在获取url的时候,并没有采用多线程,并且因为看了好几个文章,发现大神们研究的已经很透彻,所以关于进程锁的可以参考下面的参考文章,这个demo并没有考虑多个终端同一账号同时进行读写这种极端情况所需要的进程锁,也没有考虑多文件发生冲突的时候的解决方案,对于这些遗留问题可以看看下面的这些大神们怎么说,我根据感觉的好坏从上到下排序,大家可以看看哦

七、参考文章

Last modification:July 20th, 2018 at 05:25 pm
如果看了这个文章可以让你少加会班,可以请我喝杯可乐
已打赏名单
微信公众号

Leave a Comment