• iCloud开发: keyvalue Storage,CloudKit,iCloud Documents


    iCloud开发

    • 使用iCloud的开发的前提是要有开发者账号,个人或企业均可。

    iCloud三种类型的存储方式

    类型 说明
    key-value storage 键值对的存储服务,用于一些简单的数据存储
    iCloud Documents 文档存储服务,用于将文件保存到iCloud中
    CloudKit 云端数据库服务

    项目配置

    • 这些是基本配置,三种方式都需要这些配置。

    1、iCloud 官网配置

    • 配置iCloud Containers

    • 创建支持iCloud的Apple ID,并关联上相应的iCloud容器。

    • 输入Identifier即可创建完毕

    2、本地Xcode配置

    • 添加CloudKit框架到项目
    • Xcode配置信息: 选择项目->targets->Capabilities->iCloud->打开开关
    • 1、勾选自己要开启的Services
    • 2、选择对应的Containers,可以使用默认,也可以指定固定的
    • 3、观察steps是否全部success
    • 4、修改entitlements

    注意事项

    • demo是iOS和macos数据同步,所以配置稍微复杂点
    • 如果只是iOS设备间进行同步,不用修改Containers,使用默认即可,第四步不用修改
    • 其中iCloud Key-Value Store 默认是用team id和bundle identifier做标识,因为mac和ios的bundle identifier不一致,所以要手动指定为统一的

    一、key-value storage

    • 一般用于同步少量数据或者进行一些配置性质的数据同步,使用简单。
    • 使用NSUbiquitousKeyValueStore对象进行数据读写

    1、获取默认store

    	// 获取默认的store,这就是在xxx.entitlements里配置的`iCloud Key-Value Store`
    	self.keyValueStore = [NSUbiquitousKeyValueStore defaultStore];
    

    2、写入数据

    	NSLog(@"写入iCloud数据:%zd",self.number);
    	[self.keyValueStore setLongLong:self.number forKey:@"number"];
    	// 同步数据,避免冲突
    	[self.keyValueStore synchronize];
    

    3、读取数据

    	// 在获取到store后,读取iCloud数据
    	self.number = [self.keyValueStore longLongForKey:@"number"];
    

    4、监听数据改变(多台设备)

    • 需要实时知道一些配置的变更,特别是在你有多台设备时(如同时拥有iPhone和iPad)
    	// 添加监听
    	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dataChanged:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil];
        
     - (void)dataChanged:(NSNotification *)noti{
        // 监听到keyvalue值改变就会触发这个通知
        NSLog(@"keyvalue改变了:%@",noti);
        if ([noti.userInfo[NSUbiquitousKeyValueStoreChangedKeysKey] containsObject:@"number"]) {
            self.number = [noti.object longLongForKey:@"number"];
            NSLog(@"keyvalue改变了:%zd",self.number);
            
            self.myLabel.stringValue = [NSString stringWithFormat:@"%zd",self.number];
        }
    }     
    
    • iPhone上改变,mac上监听

    二、CloudKit

    iCloud官网配置

    1、选择某个容器

    2、新建Record,类似表名称

    • 需要打开recordName的索引,这样客户端才能查询数据

    3、新建Field,类似表字段

    • Filed 类型
      Filed Type 类型

    4、进入Data,创建记录

    • 创建记录后才能在客户端进行增删查改



    编码开始

    • 以上配置完毕可尝试在客户端进行增删查改。
    • 初始化容器对象
    // 初始化容器对象
    self.container = [CKContainer containerWithIdentifier:ContainerID];
    

    1、查询数据

    • 判断iCloud账户状态 accountStatusWithCompletionHandler:
    • 获取私有数据库对象 weakSelf.container.privateCloudDatabase
    • 查询数据 performQuery: inZoneWithID:completionHandler:
    	 if(self.container){
            // 访问私有数据库
            __weak typeof(self) weakSelf = self;
            [weakSelf.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) {
                // 只有登录iCloud才能读取
                if (accountStatus == CKAccountStatusAvailable) {
                    // 获取私有数据库实例
                    CKDatabase *db = weakSelf.container.privateCloudDatabase;
                    CKQuery *query  = [[CKQuery alloc] initWithRecordType:RecordType predicate:[NSPredicate predicateWithValue:YES]];
                    // 查询数据
                    [db performQuery:query inZoneWithID:nil completionHandler:^(NSArray<CKRecord *> * _Nullable results, NSError * _Nullable error) {
                        if(!error){
                            weakSelf.preOrders = [NSMutableArray arrayWithArray:results];
                            NSLog(@"%@",results);
    
                            dispatch_async(dispatch_get_main_queue(), ^{
                                [weakSelf.tableView reloadData];
                            });
                        }else{
                            NSLog(@"Error:%@",error);
                        }
                    }];
                    
                }else {
                    NSLog(@"登录iCloud错误");
                }
            }];
            
        }else {
            NSLog(@"连接iCloud错误");
        }
    
    • 返回CKRecord类型的数据,可以通过objectForKey方法直接读取
    <CKRecord: 0x101813050; recordID=FF125857-926B-4DC5-B972-9E6A6502B5A5:(_defaultZone:__defaultOwner__), recordChangeTag=jzjms8b2, values={\n    amount = 9144;\n    time = \"2021-08-20 09:33:45 +0000\";\n}, recordType=Water>
    
    
    NSDate *time = [ck objectForKey:@"time"];
    NSInteger count = [ck objectForKey:@"amount"];
    

    2、新增数据

    • 判断iCloud账户状态 accountStatusWithCompletionHandler:

    • 获取私有数据库对象 weakSelf.container.privateCloudDatabase

    • 创建Record CKRecord *record = [[CKRecord alloc] initWithRecordType:RecordType];

    • 保存数据 saveRecord: completionHandler:

          if(self.container){
              // 1 访问私有数据库
              __weak typeof(self) weakSelf = self;
              [self.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) {
                  // 1.1 只有登录iCloud才能读取
                  if (accountStatus == CKAccountStatusAvailable) {
                      // 1.2 获取私有数据库实例
                      CKDatabase *db = weakSelf.container.privateCloudDatabase;
                      // 新增数据
                      CKRecord *record = [[CKRecord alloc] initWithRecordType:RecordType];
                      
                      record[@"time"] =  [NSDate date];
                      record[@"amount"] = @(arc4random()%10000);
                      
                      // 1.3 保存数据
                      [db saveRecord:record completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) {
                          if(error) {
                              NSLog(@"%@", error);
                          } else {
                              NSLog(@"Saved successfully:%@",record);
                              dispatch_async(dispatch_get_main_queue(), ^{
                                  [weakSelf queryAction:nil];
                              });
                          }
                      }];
                  }else {
                      NSLog(@"登录iCloud错误");
                  }
              }];
          }
      
      

    3、删除数据

    • 判断iCloud账户状态 accountStatusWithCompletionHandler:
    • 获取私有数据库对象 weakSelf.container.privateCloudDatabase
    • 查询Record是否存在 fetchRecordWithID: completionHandler:
    • 删除数据 deleteRecordWithID: completionHandler:
      if(self.container){
            // 访问私有数据库
            __weak typeof(self) weakSelf = self;
            [weakSelf.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) {
                // 只有登录iCloud才能读取
                if (accountStatus == CKAccountStatusAvailable) {
                    // 获取私有数据库实例
                    CKDatabase *db = weakSelf.container.privateCloudDatabase;
                    [db fetchRecordWithID:record.recordID completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) {
                        if(error) {
                            NSLog(@"%@", error);
                        } else {
                            NSLog(@"查询成功:%@",record);
                            [db deleteRecordWithID:record.recordID completionHandler:^(CKRecordID * _Nullable recordID, NSError * _Nullable error) {
                                if(error) {
                                    NSLog(@"%@", error);
                                } else {
                                    NSLog(@"删除成功:%@",record);
                                    dispatch_async(dispatch_get_main_queue(), ^{
                                        [weakSelf.preOrders removeObjectAtIndex:indexPath.row];
                                        [weakSelf.tableView reloadData];
                                    });
                                }
                            }];
                        }
                    }];
                    
                }else {
                    NSLog(@"登录iCloud错误");
                }
            }];
            
        }else {
            NSLog(@"连接iCloud错误");
        }
    

    4、修改数据

    • 判断iCloud账户状态 accountStatusWithCompletionHandler:
    • 获取私有数据库对象 weakSelf.container.privateCloudDatabase
    • 查询Record是否存在 fetchRecordWithID: completionHandler:
    • 保存数据 saveRecord: completionHandler:
     if(self.container){
            // 访问私有数据库
            __weak typeof(self) weakSelf = self;
            NSInteger count = [weakSelf.countTextField.text integerValue];
            [weakSelf.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) {
                // 只有登录iCloud才能读取
                if (accountStatus == CKAccountStatusAvailable) {
                    // 获取私有数据库实例
                    CKDatabase *db = weakSelf.container.privateCloudDatabase;
                    [db fetchRecordWithID:weakSelf.record.recordID completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) {
                        if(error) {
                            NSLog(@"%@", error);
                        } else {
                            NSLog(@"查询成功:%@",record);
                            record[@"amount"] = @(count);
                            [db saveRecord:record completionHandler:^(CKRecord * _Nullable record, NSError * _Nullable error) {
                                if(error) {
                                    NSLog(@"%@", error);
                                } else {
                                    NSLog(@"更改成功:%@",record);
                                    dispatch_async(dispatch_get_main_queue(), ^{
                                        [[NSNotificationCenter defaultCenter] postNotificationName:@"refreshUI" object:nil];
                                        [weakSelf.navigationController popViewControllerAnimated:YES];
                                    });
                                }
                            }];
                        }
                    }];
                    
                }else {
                    NSLog(@"登录iCloud错误");
                }
            }];
            
        }else {
            NSLog(@"连接iCloud错误");
        }
    

    5、监听iCloud账户状态

        // 账户信息状态改变了会触发这个信息,可以尝试刷新数据
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dataChanged:) name:CKAccountChangedNotification object:nil];
    
    
    - (void)dataChanged:(NSNotification *)noti{
        NSLog(@"账户信息状态改变了:%@",noti);
        [self queryAction:nil];
    }
    

    CloudKit 知识扫盲

    • CKContainer 容器,或者沙盒,每个应用只能访问自己的容器。
    • CKDatabase 顾名思义,数据库了,包含私有数据库和公有数据库,用户只能访问自己的私有数据库,一些不敏感的数据也可以存储在公有数据库中。
    • CKRecord 数据记录,keyvalue形式存储的,存储一些基本类型(NSString,NSNumber,NSData,NSDate,CLLocation,CKAsset,CKReference等)
    • CKRecordZone 类似分区,是用来保存Record的。所有的Record都是保存在这里,应用有一个默认的zone,也可以自定义zone。
    • CKAsset 文件存储记录
    • CKQuery 数据库查询对象,指定查询条件进行数据查询

    三、iCloud Documents

    1、Xcode配置

    • 允许你把一份文档上传到iCloud中,然后其他设备再同步app上传的文档。

    2、自定义UIDocument

    • 首先继承UIDocument,实现自己的方法,做好NSData数据的转换
    
    #import "MyDocument.h"
    
    @implementation MyDocument
    - (instancetype)initWithFileURL:(NSURL *)url image:(UIImage *)image
    {
        if (self = [super initWithFileURL:url])
        {
            _myImage = image;
        }
        return self;
    }
    
    // 写入数据前
    - (nullable id)contentsForType:(NSString *)typeName error:(NSError **)outError{
        // 只能返回NSData 或者 NSFileWrapper ,所以这里要转换图片
        return UIImageJPEGRepresentation(_myImage, 0.7);
    }
    
    // 读取数据后
    - (BOOL)loadFromContents:(id)contents ofType:(nullable NSString *)typeName error:(NSError **)outError {
        if ([contents isKindOfClass:[NSData class]]) {
            // 如果是NSData,还要转换成图片
            _myImage = [UIImage imageWithData:contents];
        }
        return YES;
    }
    @end
      
    

    3、保存图片

    • 文件名可以随机生成
    • 查询时进行模糊查询即可全部查出来
    • 保存方法 saveToURL: forSaveOperation: completionHandler:
    - (void)saveWithImage:(UIImage *)image{
        
        if(self.baseURL){
    //        UIImage *image = [UIImage imageNamed:@"1"];
            self.localImageView.image = image;
            NSURL *bgURL = [self.baseURL URLByAppendingPathComponent:@"100JZPlg6M1DQht3xU.png"];
            MyDocument *bgImg = [[MyDocument alloc] initWithFileURL:bgURL image:image];
            [bgImg saveToURL:bgURL
            forSaveOperation:UIDocumentSaveForOverwriting
           completionHandler:^(BOOL success) {
               
               if (success)
               {
                   NSLog(@"同步成功!");
               }
               else
               {
                   NSLog(@"同步失败, 可以记录到本地等待下一次重新同步");
               }
               
           }];
        }else{
            NSLog(@"连接iCloud错误");
        }
    }
    

    4、下载图片

    
    - (IBAction)downloadClick {
        // 进行文档同步
        if(self.baseURL){
            __weak typeof(self) weakSelf = self;
            __block NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
            // 查询数据范围
            query.searchScopes = @[NSMetadataQueryUbiquitousDataScope];
            // 查询条件 NSMetadataItemFSNameKey 按照文件名搜索
    //        query.predicate = [NSPredicate predicateWithFormat:@"%K == '100JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey];
          	// 模糊查询使用 *
            query.predicate = [NSPredicate predicateWithFormat:@"%K like '*JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey];
            
            // 监听查询结果
            NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
            [center addObserverForName:NSMetadataQueryDidFinishGatheringNotification object:query queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
                
                NSLog(@"Note:%@",note);
              	// query.results 查询结果数组,如果模糊匹配可能有多个
                if (query.results.count > 0)
                {
                    NSURL *fileURL = [(NSMetadataItem *)query.results.firstObject valueForAttribute:NSMetadataItemURLKey];
                    //加载背景图片
                    MyDocument *bgImage = [[MyDocument alloc] initWithFileURL:fileURL image:nil];
                    [bgImage openWithCompletionHandler:^(BOOL success) {
                        if (success)
                        {
                            NSLog(@"下载成功!");
                            weakSelf.backgroungImageView.image = bgImage.myImage;
                        }else{
                            NSLog(@"下载失败");
                        }
                        
                    }];
                }
                // 查询完毕,关闭
                [query stopQuery];
                
            }];
            // 开启查询
            [query startQuery];
        }
    }
    

    5、Mac版代码稍微有点区别

    • 自定义NSDocument
    #import "MyDocument.h"
    
    @implementation MyDocument
    
    // 读取数据后
    - (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)typeName error:(NSError * _Nullable __autoreleasing *)outError {
        NSImage *im = [[NSImage alloc] initWithContentsOfURL:url];
        if (im) {
            self.myImage = im;
            return YES;
        }
        return NO;
    }
    // 写入数据前
    - (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
        // 只能返回NSData 或者 NSFileWrapper ,所以这里要转换图片
        NSData *data = self.myImage.TIFFRepresentation;
        return data;
    }
    - (instancetype)initWithFileURL:(NSURL *)url image:(NSImage *)image
    {
        if (self = [super initWithContentsOfURL:url ofType:@"png" error:nil])
        {
            _myImage = image;
        }
        return self;
    }
    @end
    
    • 保存图片
    - (void)saveWithImage:(NSImage *)image{
        
        if(self.baseURL){
            self.localImageView.image = image;
            NSURL *bgURL = [self.baseURL URLByAppendingPathComponent:@"100JZPlg6M1DQht3xU.png"];
            MyDocument *bgImg = [[MyDocument alloc] initWithFileURL:bgURL image:image];
            [bgImg saveToURL:bgURL ofType:@"png" forSaveOperation:NSSaveOperation completionHandler:^(NSError * _Nullable errorOrNil) {
                if (!errorOrNil)
                {
                    NSLog(@"同步成功!");
                }
                else
                {
                    NSLog(@"同步失败, 可以记录到本地等待下一次重新同步:%@",errorOrNil);
                }
            }];
        }else{
            NSLog(@"连接iCloud错误");
        }
    }
    
    • 下载图片
    - (IBAction)downloadClick:(NSButton *)btn {
        // 进行文档同步
        if(self.baseURL){
            __weak typeof(self) weakSelf = self;
            __block NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
            // 查询数据范围
            query.searchScopes = @[NSMetadataQueryUbiquitousDataScope];
            // 查询条件 NSMetadataItemFSNameKey 按照文件名搜索
            //        query.predicate = [NSPredicate predicateWithFormat:@"%K == '100JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey];
            // 模糊查询使用 *
            query.predicate = [NSPredicate predicateWithFormat:@"%K like '*JZPlg6M1DQht3xU.png'", NSMetadataItemFSNameKey];
            
            // 监听查询结果
            NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
            [center addObserverForName:NSMetadataQueryDidFinishGatheringNotification object:query queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
                
                NSLog(@"Note:%@",note);
                // query.results 查询结果数组,如果模糊匹配可能有多个
                if (query.results.count > 0)
                {
                    NSURL *fileURL = [(NSMetadataItem *)query.results.firstObject valueForAttribute:NSMetadataItemURLKey];
                    //加载背景图片
                    MyDocument *bgImage = [[MyDocument alloc] initWithFileURL:fileURL image:nil];
                    if([bgImage readFromURL:fileURL ofType:@"png" error:nil]){
                        NSLog(@"下载成功!");
                        weakSelf.localImageView.image = bgImage.myImage;
                    }
                    else{
                        NSLog(@"下载失败");
                    }
                }
                // 查询完毕,关闭
                [query stopQuery];
                
            }];
            // 开启查询
            [query startQuery];
        }
    }
    

    6、监听数据改变

    • 监听通知 NSMetadataQueryDidFinishGatheringNotification
    • 查询里面的 [query stopQuery]; 需要注释掉
        // 监听数据改变
            [center addObserverForName:NSMetadataQueryDidFinishGatheringNotification object:query queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
                
                if (query.results.count > 0)
                {
                    NSURL *fileURL = [(NSMetadataItem *)query.results.firstObject valueForAttribute:NSMetadataItemURLKey];
                    //加载背景图片
                    MyDocument *bgImage = [[MyDocument alloc] initWithFileURL:fileURL image:nil];
                    if([bgImage readFromURL:fileURL ofType:@"png" error:nil]){
                        NSLog(@"下载成功!");
                        weakSelf.localImageView.image = bgImage.myImage;
                    }
                    else{
                        NSLog(@"下载失败");
                    }
                }
                
            }];
    
  • 相关阅读:
    AJAX 类似电子表格的功能实现(续采购授权系统)
    Asp.net 程序优化
    Sql server 性能优化
    LinqToSql查询
    LInqToSql 增删改
    LinqToXml(删除某节点)
    LinqTo数组和cast,typeof的用法
    ThreadPool
    C# 定时器定时更新
    linqToXml查询
  • 原文地址:https://www.cnblogs.com/songliquan/p/16009342.html
Copyright © 2020-2023  润新知