• iOS学习笔记32-iCloud入门


    一、iCloud云服务

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

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

     

  • 相关阅读:
    P2802 【回家】
    P1706 【全排列问题】
    P1936 【水晶灯火灵】
    P1319 【压缩技术】
    P2670 【扫雷游戏】
    P1097 【统计数字】
    P1820 【寻找AP数】
    P1020 【导弹拦截】
    链表反转
    队列:队列在有限线程池中的应用
  • 原文地址:https://www.cnblogs.com/ming1025/p/6069319.html
Copyright © 2020-2023  润新知