一、iCloud云服务
iCloud是苹果提供的云端服务,用户可以将通讯录、备忘录、邮件、照片、音乐、视频等备份到云服务器并在各个苹果设备间直接进行共享而无需关心数据同步问题,甚至即使你的设备丢失后在一台新的设备上也可以通过Apple ID登录同步。
苹果已经将云端存储功能开放给开发者,可以存储两类数据:
key-value data
:
分享小量的非关键配置数据到应用的多个实例,使用类似于NSUserDefault
document
:
存储用户文档和应用数据到用户的iCloud账户
进行iCloud开发的准备工作:
- 在开发者中心上创建AppleID,启用iCloud服务
- 生成对应的配置文件(
Provisioning Profile
),这里可以使用通配Bundle ID
- 以上2步是针对真机的,调试模拟器可以忽略
- 打开项目的
Capabilities
,找到iCloud服务并开启它 - 在iCloud服务的
Service
中勾选Key-value storae
和iCloud Documents
- 你的项目中就会多出一个
entitlements
文件
- 里面的内容是自动生成的,就像这样的
- 无论真机还是模拟器,都需要进入手机的设置中登陆iCloud账号
二、Key-Value的iCloud存储
使用和NSUserDefault
差不多,都是以键值对的形式存储。
使用实例:
#import "iCloudKeysViewController.h"
@interface iCloudKeysViewController()
@property (strong, nonatomic) NSUbiquitousKeyValueStore *keyStore;
@property (strong, nonatomic) IBOutlet UILabel *textLabel;
@end
@implementation iCloudKeysViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.textLabel.text = @"Ready Go";
//获取iCloud配置首选项
self.keyStore = [NSUbiquitousKeyValueStore defaultStore];
//注册通知中心,当配置发生改变的时候,发生通知
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(ubiquitousKeyValueStoreDidChange:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:keyStore];
}
/* UI点击,点击改变按钮 */
- (IBAction)changeKey
{
[self.keyStore setString:@"Hello World" forKey:@"MyString"];
[self.keyStore synchronize];
NSLog(@"Save key");
}
/* 监听通知,当配置发生改变的时候会调用 */
- (void)ubiquitousKeyValueStoreDidChange:(NSNotification *)notification
{
NSLog(@"External Change detected");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Change detected"
message:@"Change detected"
delegate:nil
cancelButtonTitle:@"Ok"
otherButtonTitles:nil, nil];
[alert show];
//显示改变的信息
textLabel.text = [keyStore stringForKey:@"MyString"];
}
@end
三、Document的iCloud存储
核心类UIDocument
- 文档存储主要是使用
UIDocument
类来完成,这个类提供了新建、修改、查询文档、打开文档、删除文档的功能。 UIDocument
对文档的新增、修改、删除、读取全部基于一个云端URL来完成,对于开发者而言没有本地和云端之分,这样大大简化了开发过程。
云端URL的获取方式:
-
调用
NSFileManager
的对象方法-(NSURL *)URLForUbiquityContainerIdentifier:(NSString *)identifier;
-
上面需要传递一个云端存储容器的唯一标示,这个可以去自动生成的
entitlements
文件查看Ubiquity Container Identifiers
字段获得,如果传nil
,代表第一个容器
补充知识 :
默认的第一个容器标识是
iCloud.$(CFBundleIdentifier)
,
其中$(CFBundleIdentifier)
代表Bundle ID
,那么根据应用的Bundle ID
就可以得知我的第一个容器的标识是iCloud.com.liuting.icloud.iCloudTest
UIDocument的对象方法:
/* 将指定URL的文档保存到iCloud(可以是新增或者覆盖,通过saveOperation参数设定)*/
- (void)saveToURL:(NSURL *)url
forSaveOperation:(UIDocumentSaveOperation)saveOperation
completionHandler:(void (^)(BOOL success))completionHandler;
/* 保存操作option */
typedef NS_ENUM(NSInteger, UIDocumentSaveOperation) {
UIDocumentSaveForCreating,/* 创建 */
UIDocumentSaveForOverwriting/* 覆盖写入 */
};
/* 打开文档,参数是打开文档成功回调 */
- (void)openWithCompletionHandler:(void (^)(BOOL success))completionHandler;
删除文档使用的是NSFileManager的对象方法:
/* 删除指定URL下的文件 */
- (BOOL)removeItemAtURL:(NSURL *)URL
error:(NSError **)error;
注意事项:
-
UIDocument
在设计的时候,没有提供统一的存储方式来存储数据,需要我们去继承它,重写2个对象方法自己操作数据/* 保存文档时调用 @param typeName 文档文件类型 @param outError 错误信息输出 @return 文档数据 */ -(id)contentsForType:(NSString *)typeName error:(NSError **)outError; /* 读取数据时调用 @param contents 文档数据 @param typeName 文档文件类型 @param outError 错误信息输出 @return 读取是否成功 */ -(BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError;
UIDocument
保存数据的本质:
将A对应类型的数据转化为云端存储的NSData
或者NSFileWrapper
数据-
UIDocument
读取数据的本质:
将云端下载的NSData
或者NSFileWrapper
数据转化为A对应类型的数据
下面是我自定义的Document类,继承于UIDocument:
LTDocument.h文件
#import <UIKit/UIKit.h>
@interface LTDocument : UIDocument
@property (strong, nonatomic) NSData *data;/*< 文档数据 */
@end
LTDocument.m文件
#import "LTDocument.h"
@implementation LTDocument
#pragma mark - 重写父类方法
/**
* 保存时调用
* @param typeName 文档文件类型后缀
* @param outError 错误信息输出
* @return 文档数据
*/
- (id)contentsForType:(NSString *)typeName
error:(NSError *__autoreleasing *)outError
{
if (!self.data) {
self.data = [NSData data];
}
return self.data;
}
/**
* 读取数据时调用
* @param contents 文档数据
* @param typeName 文档文件类型后缀
* @param outError 错误信息输出
* @return 读取是否成功
*/
- (BOOL)loadFromContents:(id)contents
ofType:(NSString *)typeName
error:(NSError *__autoreleasing *)outError
{
self.data = [contents copy];
return true;
}
@end
- 如果要加载iCloud中的文档列表,就需要使用另一个类
NSMetadataQuery
- 通常考虑到网络的原因并不会一次性加载所有数据,而利用
NSMetadataQuery
并指定searchScopes
为NSMetadataQueryUbiquitousDocumentScope
来限制查找iCloud文档数据。 - 使用
NSMetadataQuery
还可以通过谓词限制搜索关键字等信息,并在搜索完成之后通过通知的形式通知客户端搜索的情况。
下面是使用示例:
1. 属性定义和宏定义:
#import "ViewController.h"
#import "LTDocument.h"
#define kContainerIdentifier @"iCloud.com.liuting.icloud.iCloudTest"
@interface ViewController () <UITableViewDataSource,UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITextField *documentField;/*< 输入框 */
@property (weak, nonatomic) IBOutlet UILabel *documentShowLable;/*< 显示栏 */
@property (weak, nonatomic) IBOutlet UITableView *documentTableView;/* 文档列表 */
/* 文档文件信息,键为文件名,值为创建日期 */
@property (strong, nonatomic) NSMutableDictionary *files;
@property (strong, nonatomic) NSMetadataQuery *query;/*< 查询文档对象 */
@property (strong, nonatomic) LTDocument *document;/*< 当前选中文档 */
@end
2. 获取云端URL方法:
/**
* 取得云端存储文件的地址
* @param fileName 文件名,如果文件名为nil,则重新创建一个URL
* @return 文件地址
*/
- (NSURL *)getUbiquityFileURL:(NSString *)fileName{
//取得云端URL基地址(参数中传入nil则会默认获取第一个容器),需要一个容器标示
NSFileManager *manager = [NSFileManager defaultManager];
NSURL *url = [manager URLForUbiquityContainerIdentifier:kContainerIdentifier];
//取得Documents目录
url = [url URLByAppendingPathComponent:@"Documents"];
//取得最终地址
url = [url URLByAppendingPathComponent:fileName];
return url;
}
3. 查询文档列表方法
/* 从iCloud上加载所有文档信息 */
- (void)loadDocuments
{
if (!self.query) {
self.query = [[NSMetadataQuery alloc] init];
self.query.searchScopes = @[NSMetadataQueryUbiquitousDocumentsScope];
//注意查询状态是通过通知的形式告诉监听对象的
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(metadataQueryFinish:)
name:NSMetadataQueryDidFinishGatheringNotification
object:self.query];//数据获取完成通知
[center addObserver:self
selector:@selector(metadataQueryFinish:)
name:NSMetadataQueryDidUpdateNotification
object:self.query];//查询更新通知
}
//开始查询
[self.query startQuery];
}
/* 查询更新或者数据获取完成的通知调用 */
- (void)metadataQueryFinish:(NSNotification *)notification
{
NSLog(@"数据获取成功!");
NSArray *items = self.query.results;//查询结果集
self.files = [NSMutableDictionary dictionary];
//变量结果集,存储文件名称、创建日期
[items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSMetadataItem *item = obj;
//获取文件名
NSString *fileName = [item valueForAttribute:NSMetadataItemFSNameKey];
//获取文件创建日期
NSDate *date = [item valueForAttribute:NSMetadataItemFSContentChangeDateKey];
NSDateFormatter *dateformate = [[NSDateFormatter alloc]init];
dateformate.dateFormat = @"YY-MM-dd HH:mm";
NSString *dateString = [dateformate stringFromDate:date];
//保存文件名和文件创建日期
[self.files setObject:dateString forKey:fileName];
}];
//表格刷新
self.documentShowLable.text = @"";
[self.documentTableView reloadData];
}
4. UI点击事件
#pragma mark - UI点击事件
/* 点击添加文档 */
- (IBAction)addDocument:(id)sender {
//提示信息
if (self.documentField.text.length <= 0) {
NSLog(@"请输入要创建的文档名");
self.documentField.placeholder = @"请输入要创建的文档名";
return;
}
//创建文档URL
NSString *text = self.documentField.text;
NSString *fileName = [NSString stringWithFormat:@"%@.txt",text];
NSURL *url = [self getUbiquityFileURL:fileName];
//创建云端文档对象
LTDocument *document = [[LTDocument alloc] initWithFileURL:url];
//设置文档内容
NSString *dataString = @"hallo World";
document.data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
//保存或创建文档,UIDocumentSaveForCreating是创建文档
[document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success)
{
if (success) {
NSLog(@"创建文档成功.");
self.documentField.text = @"";
//从iCloud上加载所有文档信息
[self loadDocuments];
}else{
NSLog(@"创建文档失败.");
}
}];
}
/* 点击修改文档 */
- (IBAction)saveDocument:(UIButton *)sender {
if ([sender.titleLabel.text isEqualToString:@"修改文档"]) {
self.documentField.text = self.documentShowLable.text;
[sender setTitle:@"保存文档" forState:UIControlStateNormal];
} else if([sender.titleLabel.text isEqualToString:@"保存文档"]) {
[sender setTitle:@"修改文档" forState:UIControlStateNormal];
self.documentField.placeholder = @"请输入修改的文档内容";
//要保存的文档内容
NSString *dataText = self.documentField.text;
NSData *data = [dataText dataUsingEncoding:NSUTF8StringEncoding];
self.document.data = data;
//保存或创建文档,UIDocumentSaveForOverwriting是覆盖保存文档
[self.document saveToURL:self.document.fileURL
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success)
{
NSLog(@"保存成功!");
self.documentShowLable.text = self.documentField.text;
self.documentField.text = @"";
}];
}
}
/* 点击删除文档 */
- (IBAction)removeDocument:(id)sender {
//提示信息
if (self.documentField.text.length <= 0) {
self.documentField.placeholder = @"请输入要删除的文档名";
return;
}
//判断要删除的文档是否存在
NSString *text = self.documentField.text;
NSString *fileName = [NSString stringWithFormat:@"%@.txt",text];
NSArray *fileNames = [self.files allKeys];
if (![fileNames containsObject:fileName]) {
NSLog(@"没有要删除的文档");
return;
}
//创建要删除的文档URL
NSURL *url = [self getUbiquityFileURL:fileName];
NSError *error = nil;
//删除文档文件
[[NSFileManager defaultManager] removeItemAtURL:url error:&error];
if (error) {
NSLog(@"删除文档过程中发生错误,错误信息:%@",error.localizedDescription);
return;
}
//从集合中删除
[self.files removeObjectForKey:fileName];
self.documentField.text = @"";
}
5. 视图控制器初始化和列表显示
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.documentTableView.delegate = self;
self.documentTableView.dataSource = self;
/* 从iCloud上加载所有文档信息 */
[self loadDocuments];
}
#pragma mark - UITableView数据源
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return self.files.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identtityKey = @"myTableViewCellIdentityKey1";
UITableViewCell *cell =
[self.documentTableView dequeueReusableCellWithIdentifier:identtityKey];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
reuseIdentifier:identtityKey];
}
//显示文档名和文档创建日期
NSArray *fileNames = self.files.allKeys;
NSString *fileName = fileNames[indexPath.row];
cell.textLabel.text = fileName;
cell.detailTextLabel.text = [self.files valueForKey:fileName];
return cell;
}
#pragma mark - UITableView代理方法
/* 点击文档列表的其中一个文档调用 */
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.documentTableView cellForRowAtIndexPath:indexPath];
//获取文档URL
NSURL *url = [self getUbiquityFileURL:cell.textLabel.text];
//创建文档操作对象
LTDocument *document = [[LTDocument alloc] initWithFileURL:url];
self.document = document;
//打开文档并读取文档内容
[document openWithCompletionHandler:^(BOOL success) {
if(success){
NSLog(@"读取数据成功.");
NSString *dataText = [[NSString alloc] initWithData:document.data
encoding:NSUTF8StringEncoding];
self.documentShowLable.text = dataText;
}else{
NSLog(@"读取数据失败.");
}
}];
}
@end