在iOS平台上,基于iCloud的开发不需要搭建服务器,开发个人软件的同步很方便。很早之前提过iCloud的使用:《iOS开发之iCloud开发(数据与文档的读写删除)》,那时候主要说了字符串的读取和更新,以及文档的读写,现在再来整体完善下大文档的过程。

上个文章的遗漏:没有自动同步

首先在iCloud同步过程中有两个最重要的通知:

  • NSMetadataQueryDidFinishGatheringNotificationiCloud的内容获取完成
  • NSMetadataQueryDidUpdateNotificationiCloud的数据有更新

在上个文章中,只监听了iCloud的内容数据获取完成,然后手动下载,而在iCloud的数据有更新的时候,没有做任何事情,所以这样的话只能手动下载,并不能监听然后实现自动同步数据。

iCloud同步数据的过程

这张图可以很清晰的表达出真机的同步过程

空白.png

在理想状态下,如果设备2同步数据到iCloud,那么应该是其他没有同步过的设备获取到更新通知。但是现实真机的情况却是设备2同步数据到iCloud之后,除了其他设备,同样自己也会接收到iCloud数据更新的通知。

这样如果不处理,自己同时监听iCloud更新和内容获取完成后使用openWithCompletionHandler读取iCloud内容的话,就会出现:读取》更新》读取》更新》读取……这样的死循环。

读取下载iCloud数据到本地

老派的写法

在iOS6和之前的设备时,就是现在网上最多的那些写法就是在获取到数据的时候去停止刷新,更新使用之后再去开启刷新的功能

//获取数据成功
-(void)MetadataQueryDidFinishGathering:(NSNotification*)noti {
    //关闭更新
    [self.m_metadataQuery disableUpdates]
    //读写使用数据
    // ......
    // ......
    //开启更新
    [self.m_metadataQuery enableUpdates]
}

现在搜索iCloud的相关文章,这种几年前的写法很常见,而其他的很多就是转帖,居然没有见到几个原创的新的,可能是这个需求不强烈吧。

正确的姿势

disableUpdates中说

Unless you use enumerateResultsUsingBlock: or enumerateResultsWithOptions:usingBlock:, you should invoke this method before iterating over query results that could change due to live updates.

所以既然这么麻烦,直接使用新的api接口即可。通过这两个接口去遍历即可不用调用停止更新,然后再开启更新

enumerateResultsUsingBlock: 
enumerateResultsWithOptions:usingBlock:
[self.m_metadataQuery enumerateResultsUsingBlock:^(id  _Nonnull result, NSUInteger idx, BOOL * _Nonnull stop) {
     //遍历所有的iCloud数据
 }];

获取到iCloud数据的结果遍历的时候,我们需要根据文件的状态去更新本地的数据,通过结果可以知道文件的状态

[self.m_metadataQuery enumerateResultsUsingBlock:^(id  _Nonnull result, NSUInteger idx, BOOL * _Nonnull stop) {
     //遍历所有的iCloud数据
     NSMetadataItem*item =result;
     //获取文件更新状态
       NSURL *fileURL = [result valueForAttribute:NSMetadataItemURLKey];
       NSString *fileStatus;
       [fileURL getResourceValue:&fileStatus forKey:NSURLUbiquitousItemDownloadingStatusKey error:nil];
 }];

文件有下面三个状态

  • NSURLUbiquitousItemDownloadingStatusNotDownloaded : 该文件新的数据还没有下载,需要自己调用[[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:fileURL error:&error];去下载
  • NSURLUbiquitousItemDownloadingStatusDownloaded : 有新的版本,马上要下载,这个意思是说iCloud有新的数据了,可能是其他设备同步上去的
  • NSURLUbiquitousItemDownloadingStatusCurrent : 本地已经是这个文件的最新版本,这时候就要根据之前这个文件的状态去判断,如果之前的文件状态不是NSURLUbiquitousItemDownloadingStatusCurrent,现在是NSURLUbiquitousItemDownloadingStatusCurrent ,那就是说已经把最新版本的下载到本地了,需要去更新本地的数据。而如果一直都是NSURLUbiquitousItemDownloadingStatusCurrent,那就说明是这个设备自己更新上去的,这时候就不需要再去更新本地数据了

通过这个文件状态的判断,就是去切断update > read > update > read >……的方式

代码展示

//获取数据成功
-(void)MetadataQueryDidFinishGathering:(NSNotification*)noti {
    NSLog(@"iCloud数据查询结束");
    NSArray *items = self.m_metadataQuery.results;//查询结果集
    if (!self.m_documentUrl) {
        NSLog(@"手机系统设置中未开启iCloud或者未登录iCloud账户");
        return;
    }
    if (!items || items.count == 0) {
        NSLog(@"iCloud没有数据");
       return;
    }

    //要更新的数据量,比如是三个
    self.m_fileNameArray = [NSMutableArray array];
    
    //便利结果
    [self.m_metadataQuery enumerateResultsUsingBlock:^(id  _Nonnull result, NSUInteger idx, BOOL * _Nonnull stop) {
        NSMetadataItem*item =result;
        //获取文件名
        NSString *fileName = [item valueForAttribute:NSMetadataItemFSNameKey];
        //获取文件更新日期
        NSDate *date = [item valueForAttribute:NSMetadataItemFSContentChangeDateKey];
        //获取文件更新状态
        NSURL *fileURL = [result valueForAttribute:NSMetadataItemURLKey];
        NSString *fileStatus;
        [fileURL getResourceValue:&fileStatus forKey:NSURLUbiquitousItemDownloadingStatusKey error:nil];
        
        NSLog(@"拉取到的信息: %@,%@,%@",fileName,date,fileStatus);
        if ([fileStatus isEqualToString:NSURLUbiquitousItemDownloadingStatusDownloaded]) {
            // File will be updated soon
            [SVProgressHUD showWithStatus:NSLocalizedString(@"iCloud数据正在更新", nil)];
            //有其他设备更新了iCloud
            self.shouldUpdateLocalData = true;
        }
        if ([fileStatus isEqualToString:NSURLUbiquitousItemDownloadingStatusCurrent]) {
            if (self.shouldUpdateLocalData) {
                [self.m_fileNameArray addObject:fileName];
                //当三个数据全都是最新的,并且是其他设备更新了iCloud,那就更新这个设备的数据
                if (self.m_fileNameArray.count == 3) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        self.shouldUpdateLocalData = false;
                        [self handleDataWithFileName];
                        NSLog(@"iCloud数据下载同步完成");
                    });
                }
            } else {
                //本地是最新的,是本机更新的数据到iCloud
            }
        }else if ([fileStatus isEqualToString:NSURLUbiquitousItemDownloadingStatusNotDownloaded]) {
            NSError *error;
            BOOL downloading = [[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:fileURL error:&error];
            NSLog(@"开始下载: %d",downloading);
            //有其他设备更新了iCloud
            self.shouldUpdateLocalData = true;
        }
    }];
}

- (void)handleDataWithFileName {
    for (NSString *fileName in self.m_fileNameArray) {
    //读取文件内容
    HDWEAKSELF;
    HDCloudDocument *doc =[[HDCloudDocument alloc] initWithFileURL:[self getUbiquityContainerUrl:fileName]];

    if (doc.documentState & UIDocumentStateClosed) {
        NSLog(@"打开Document数据");
        [doc openWithCompletionHandler:^(BOOL success) {
            if (success) {
                    //将iCloud的数据写到本地
                dispatch_async(dispatch_get_main_queue(), ^{
                    [weakSelf writeToFileWithFileName:fileName withData:[NSData dataWithData:doc.myData]];
                });
            } else {
                HDDebugLog(@"下载出错");
                if (weakSelf.isShowDownloadProgress) {
                    [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"下载iCloud数据出错,请检查手机网络", nil)];
                }
            }
        }];
    } else if (doc.documentState & UIDocumentStateNormal) {
        NSLog(@"Document already opened, retrieving content");
        //将iCloud的数据写到本地
        [self writeToFileWithFileName:fileName withData:[NSData dataWithData:doc.myData]];
    } else if (doc.documentState & UIDocumentStateEditingDisabled) {
        NSLog(@"Document editing disabled.");
        //将iCloud的数据写到本地
        [self writeToFileWithFileName:fileName withData:[NSData dataWithData:doc.myData]];
    }
    }
}

上传本地的数据到iCloud

上传基本没有什么需要特殊的,更改和写入即可,下面代码可以忽略回调

//上传数据
- (void)uploadDataBase:(NSData *)data  completionHandler:(uploadComplete)complete {
    NSURL *cloudDataUrl = [self getUbiquityContainerUrl:ListModel_db];;
    if (!self.m_documentUrl) {
        if (complete) {
            complete(false);
        }
        return;
    }
    HDCloudDocument *doc = [[HDCloudDocument alloc] initWithFileURL:cloudDataUrl];
    if (!data) {
        data = [NSData data];
    }
     doc.myData = data;
    [doc updateChangeCount:UIDocumentChangeDone];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:[cloudDataUrl path]]) {
        [doc saveToURL:doc.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
            if (success) {
                [doc closeWithCompletionHandler:^(BOOL success) {
                    if (complete) {
                        complete(success);
                    }
                }];
            } else {
                if (complete) {
                    complete(false);
                }
            }
        }];
    } else {
        [doc saveToURL:doc.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
            if (success) {
                [doc closeWithCompletionHandler:^(BOOL success) {
                    if (complete) {
                        complete(success);
                    }
                }];
            } else {
                if (complete) {
                    complete(false);
                }
            }
        }];
    }
    
    
}

总结

其他的就不需要多说了,基础的内容在这个文章里面《iOS开发之iCloud开发(数据与文档的读写删除)》,希望对大家有所帮助。谢谢

参考文章


☟☟可点击下方广告支持一下☟☟

最后修改:2018 年 07 月 23 日
请我喝杯可乐,请随意打赏: ☞已打赏列表