iCloud开发实践
写在前面
最近在一直在研究iCloud开发相关的东西,觉得是有必要写篇总结来整理一下近段时间的一些学习成果。之前一直听说iCloud服务不友好也不完善,开发难度也相对较大,其实个人觉得貌似也没说错,iCloud在客户端提供的框架相比于其他的功能框架来说,他是被分散到各个框架中,要使用它必须要了解各个部分的功能及使用场合,这样就增加了学习的成本。同时,框架的设计也比较松散,特别是文档同步,虽然提供了灵活功能强大的框架,但是要理清楚还是需要时间去学习和实践。
为了让大家少走弯路,我将iCloud划分了几大功能模块,下面会逐个地讲述每个模块的一些基本使用,同时配备例子进行说明:
准备工作
想要使用iCloud服务我们必须要有一个苹果的开发者账号(99$个人或者企业都可以),然后需要为项目进行一些配置:
- 在Xcode中点击项目目录结构的根节点进入项目设置
- 在Capabilities页签中找到iCloud一项,然后将对应该项的开关设置为开启状态。
- 在iCloud一栏下的有Services和Containers两个小栏目,其中Services中有三个选项,其对应说明如下:
名称 | 说明 |
---|---|
Key-value storage | 键值对的存储服务,用于一些简单的数据存储 |
iCloud Documents | 文档存储服务,用于将文件保存到iCloud中 |
CloudKit | 云端数据库服务 |
这三种服务会在后续章节为大家详细进行讲解。然后就是Container这一栏,顾名思义,其实可以简单认为他是用于存放数据的地方,因为每个应用所存放的数据应该是独立的同时也具有沙箱的限制,所以iOS为每个应用开辟了一个独立的空间来存放在iCloud的文件或数据,同时也方便从iCloud上同步数据到这个地方。默认情况下,一旦开启iCloud服务,就会创建一个默认的容器,其命名为iCloud + BundleID
。如果你不想要使用默认的容器又或者想跟自己开发的其他App共享文件数据,则可以选择Specify custom containers
选项,然后在容器列表中选择一个指定的容器,或者点击+号创建一个新的容器。
配置完成后,效果如图所示:
注意:iCloud下面的Steps必须都打上勾才表示正常启用服务,否则需要根据提示检查你的苹果开发者账号中的一些应用设置。
在正式开始前还有一个事情要说清楚的,这里仅仅讨论的是使用iCloud作为登录账号的app,如果你的app有自己的用户系统,那么你还需要将同步的数据进行标识(例如加个系统用户标识来确定那份数据是哪个用户的),然后根据标识进行数据合并。好了,下面可以开始讲述一些具体开发过程(敲代码时间到了~)
Key-value同步
该种方式一般用于同步少量数据或者进行一些配置性质的数据同步。其使用也比较简单,iOS提供了一个NSUbiquitousKeyValueStore
的类型来实现相关的操作。它的使用跟NSUserDefaults
类似。主要提供以下的功能:
名称 | 说明 |
---|---|
defaultStore |
返回NSUbiquitousKeyValueStore 对象,用于Key-value的存取操作 |
objectForKey: |
获取指定key的值 |
setObject:forKey: |
设置指定key的值 |
removeObjectForKey: |
移除指定键值 |
stringForKey: |
获取指定key保存的字符串,如果指定key不存在或者对应key保存的值不是NSString 类型时则返回nil |
setString:forKey: |
为指定key设置一个字符串 |
arrayForKey: |
获取指定key保存的数组,如果指定key不存在或者对应key保存的值不是NSArray 类型时则返回nil |
setArray:forKey: |
为指定key设置一个数组对象 |
dictionaryForKey: |
获取指定key保存的字典,如果指定key不存在或者对应key保存的值不是NSDictionary 类型时则返回nil |
setDictionary:forKey: |
为指定key设置一个字典对象 |
dataForKey: |
获取指定key保存的二进制数组,如果指定key不存在或者对应key保存的值不是NSData 类型时则返回nil |
setData:forKey: |
为指定key设置一个二进制数组 |
longLongForKey: |
获取指定key保存的64位整型值,如果指定key不存在或者对应key保存的值不包含数值时则返回0 |
setLongLong:forKey: |
为指定key设置一个64位整型值 |
doubleForKey: |
获取指定key保存的浮点数值,如果指定key不存在或者对应key保存的值不包含数值时则返回0 |
setDouble:forKey: |
为指定key设置一个浮点数值 |
boolForKey: |
获取指定key保存的布尔值,如果指定key不存在则返回NO |
setBool:forKey: |
为指定key设置一个布尔值 |
synchronize |
同步数据,将在内存中的数据同步到磁盘中,并上传至iCloud |
dictionaryRepresentation |
该属性会返回载入到内存中保存的key-value字典,如果想要最新的数据则需要先调用synchronize 方法 |
下面我们来举个例子,看看如何使用NSUbiquitousKeyValueStore
进行数据同步。
首先,我们在界面中拖入两个按钮,一个用于设置数据,一个用于获取数据,如图所示:
然后在VC中声明一个NSUbiquitousKeyValueStore
类型的属性,并且把两个按钮与VC的按钮点击事件进行关联,其中VC代码如下:
@interface KeyValueViewController ()
// Key-value同步数据存储对象
@property (nonatomic, strong) NSUbiquitousKeyValueStore *keyValueStore;
@end
@implementation KeyValueViewController
- (IBAction)setValueButtonClickedHandler:(id)sender
{
// 设置值按钮点击事件
}
- (IBAction)getValueButtonClickedHandler:(id)sender
{
// 获取值按钮点击事件
}
然后在viewDidLoad
方法中对keyValueStore
进行初始化:
- (void)viewDidLoad
{
[super viewDidLoad];
self.keyValueStore = [NSUbiquitousKeyValueStore defaultStore];
}
然后分别实现两个按钮的点击事件:
- (IBAction)setValueButtonClickedHandler:(id)sender
{
[self.keyValueStore setString:@"Hello iCloud" forKey:@"data"];
[self.keyValueStore synchronize];
}
- (IBAction)getValueButtonClickedHandler:(id)sender
{
NSString *string = [self.keyValueStore stringForKey:@"data"];
NSLog(@"data = %@", string);
}
上面的代码用到了NSUbiquitousKeyValueStore
的字符串存取方法,要注意的是当你设置了数据后一定要调用synchronize
方法,否则这些设置操作是不会保存下来并且上传到iCloud上的。
该例子最好是能够准备两台设备(或模拟器)来进行测试,一台进行值设置,另外一台进行值的获取。
有时候,我们需要实时知道一些配置的变更,特别是在你有多台设备时(如同时拥有iPhone和iPad),想要在其中一台设备中变更某项信息,然后另外一台设备也能够感知并作出相应的调整。那么,这时候你需要监听NSUbiquitousKeyValueStoreDidChangeExternallyNotification
通知,它能够告诉你的App所保存的key-value有变更。
我们将上面的例子进行改造,将设置字符串改为设置一个背景颜色值,并且设定它的key为bg
,然后通过监听NSUbiquitousKeyValueStoreDidChangeExternallyNotification
通知来改变VC的视图背景颜色。
首先,我们在viewDidLoad
中进行监听通知:
- (void)viewDidLoad
{
[super viewDidLoad];
// 初始化keyValueStore
self.keyValueStore = [NSUbiquitousKeyValueStore defaultStore];
// 监听通知
[[NSNotificationCenter defaultCenter] addObserverForName:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:self.keyValueStore queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
if ([note.userInfo[NSUbiquitousKeyValueStoreChangedKeysKey] containsObject:@"bg"])
{
long long bgColorValue = [self.keyValueStore longLongForKey:@"bg"];
UIColor *bgColor = [UIColor colorWithRed:(bgColorValue & 0xff) / 0xff
green:(bgColorValue >> 8 & 0xff) / 0xff
blue:(bgColorValue >> 16 & 0xff) / 0xff
alpha:1];
self.view.backgroundColor = bgColor;
}
}];
}
在上面代码中的通知处理,我们先通过NSUbiquitousKeyValueStoreChangedKeysKey
来判断变更的key中是否包含bg
这个key,如果存在则表示背景颜色有变更,再从keyValueStore
中取出颜色值并转换成UIColor
对象并设置成视图的背景颜色。
同时,两个按钮的点击事件处理如下:
- (IBAction)setValueButtonClickedHandler:(id)sender
{
[self.keyValueStore setLongLong:0x00ff00 forKey:@"bg"];
[self.keyValueStore synchronize];
}
- (IBAction)getValueButtonClickedHandler:(id)sender
{
long long bgColorValue = [self.keyValueStore longLongForKey:@"bg"];
UIColor *bgColor = [UIColor colorWithRed:(bgColorValue & 0xff) / 0xff
green:(bgColorValue >> 8 & 0xff) / 0xff
blue:(bgColorValue >> 16 & 0xff) / 0xff
alpha:1];
self.view.backgroundColor = bgColor;
}
通过上面的改动,在测试的过程中如果其中一台设备点击了set value按钮,则另外一台设备就会收到通知,并且变更视图的背景颜色。
文档数据同步
有时候会有这样的需求,如果开发的app是一款阅读类工具或者是一款壁纸工具,那么,我们会希望用户所下载的书籍或者壁纸会同步到不同的设备上来方便用户的操作,不需要再次去找到这本书或者这张图片进行重新下载。那么,iCloud提供了这样的服务,允许你把一份文档上传到iCloud中,然后其他设备再同步app上传的文档。
要想使用文档数据同步服务,就需要配合UIDocument
来完成这项工作,具体的处理流程我先简单的描述一下,这样可以快速帮助到大家来理解机制的运作。
- 为
UIDocument
创建一个子类,该类型主要对app的中的文档进行管理。 - 重写
UIDocument
的contentsForType:error:
和loadFromContents:ofType:error:
方法,让文档根据app内部机制来实现保存和读取。 - 通过
UIDocument
的saveToURL:forSaveOperation:completionHandler:
将文档保存到iCloud容器中。 - 其他设备可以
NSMetadataQuery
来获取iCloud容器的文档列表,并更新到本地。
这里要注意一个问题,因为涉及到网络同步等相关的一些列操作,并不仅仅是当前应用进程在访问文件,系统的进程和其他应用进程也会对相关文件进行处理,所以不能通过
NSFileManager
直接对iCloud容器中的文件进行操作。同时也要弄清楚一个概念,其实
UIDocument
并不是为iCloud而设,它同样可以管理本地的文档。唯一区别是如果你的文档要放到iCloud,那么传给UIDocument
的文档路径必须是以iCloud容器地址开始的路径,这样才能实现文档的同步。
那么,下面来举例介绍如何进行文档数据的同步,假设开发的app是一款壁纸应用,在应用壁纸时会下载图片,然后讲它保存到iCloud中。另外的设备就可以自动地同步下载的图片并应用该壁纸。
首先把界面给搭建起来,如下图所示:
界面是使用UICollectionView
搭建的,代码在这里就不贴上来了,主要关注设置图片背景的处理过程。
首先,继承UIDocument
类型创建其一个子类BackgroundImage
,并为BackgroundImage
声明一个传入UIImage
对象的构造方法以及重写contentsForType:error:
和loadFromContents:ofType:error:
两个方法代码如下:
@interface BackgroundImage : UIDocument
// 图片对象
@property (nonatomic, strong, readonly) UIImage *image;
// 构造方法
- (instancetype)initWithFileURL:(NSURL *)url image:(UIImage *)image;
@end
@implementation BackgroundImage
- (instancetype)initWithFileURL:(NSURL *)url image:(UIImage *)image
{
if (self = [super initWithFileURL:url])
{
_image = image;
}
return self;
}
- (id)contentsForType:(NSString *)typeName error:(NSError * _Nullable __autoreleasing *)outError
{