iOS学习笔记32-iCloud入门

一、iCloud云服务

iCloud是苹果提供的云端服务,用户可以将通讯录、备忘录、邮件、照片、音乐、视频等备份到云服务器并在各个苹果设备间直接进行共享而无需关心数据同步问题,甚至即使你的设备丢失后在一台新的设备上也可以通过Apple ID登录同步。

苹果已经将云端存储功能开放给开发者,可以存储两类数据:
  1. key-value data
    分享小量的非关键配置数据到应用的多个实例,使用类似于NSUserDefault
  1. document
    存储用户文档和应用数据到用户的iCloud账户
进行iCloud开发的准备工作:
  1. 在开发者中心上创建AppleID,启用iCloud服务
  1. 生成对应的配置文件(Provisioning Profile),这里可以使用通配Bundle ID
  2. 以上2步是针对真机的,调试模拟器可以忽略
  3. 打开项目的Capabilities,找到iCloud服务并开启它
  4. 在iCloud服务的Service中勾选Key-value storaeiCloud Documents
    以上是使用模拟器的,使用真机的话,下面2个红色感叹就会消失
  5. 你的项目中就会多出一个entitlements文件
  6. 里面的内容是自动生成的,就像这样的


  7. 无论真机还是模拟器,都需要进入手机的设置中登陆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并指定searchScopesNSMetadataQueryUbiquitousDocumentScope来限制查找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

上面的代码Demo点这里:LearnDemo里面的iCloudDemo
这个代码Demo只有Document的实例代码

如果有什么问题请在下方评论区中提出!O(∩_∩)O哈!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容