CloudKit的數(shù)據(jù)存儲(chǔ)分為兩種:
一種是使用iCloudKit,其API的使用有點(diǎn)像sqlite;
一種是使用iCloud Documents, 這個(gè)有點(diǎn)像APP的本地沙盒;
真對(duì)第一種可參考[iCloud]項(xiàng)目?jī)?nèi)啟用iCloud及CloudKit Dashboard介紹了解,今天主要講的是第二種: iCloud Documents;
一, 啟用iCloud
首先,新建項(xiàng)目后,要確保你的Apple ID是一個(gè)有效的開(kāi)發(fā)者賬號(hào);并在General -->Identity
下的Team選項(xiàng),選擇你的開(kāi)發(fā)者賬號(hào),這里的開(kāi)發(fā)者賬號(hào),必須是有效的開(kāi)發(fā)者賬號(hào),并確保你的Bundle Identifier
是唯一的;
然后,設(shè)置權(quán)限和容器,選擇Capabilities-->啟用iCloud,如下圖所示:
如果之前沒(méi)有選擇開(kāi)發(fā)者賬號(hào)的話,這時(shí),可能需要你登陸開(kāi)發(fā)者賬號(hào);
最后,勾選iCloud Documents
,這時(shí)Containers
下的選項(xiàng)就可點(diǎn)了,選擇Use default container
:
編譯一下,沒(méi)有錯(cuò)誤,即開(kāi)啟成功!
注意: 這里的開(kāi)發(fā)者賬號(hào)要有相應(yīng)的證書(shū),而且證書(shū)的Apple ID中啟用了iCloud:
appleid啟用iCloud
如果,已有id, 可以點(diǎn)擊Edit進(jìn)行啟用.
二. 數(shù)據(jù)操作
今天要講的這種操作數(shù)據(jù)的方法主要是使用了NSFileManager,當(dāng)您看到這個(gè),是不是就安心了許多? 是的, 和操作本地文件有很多相似的地方, 只是使用的API不同.
2.1 檢查iCloud是否可用
在進(jìn)行數(shù)據(jù)操作之前,一定要確保iCloud是可用的, 如果不可用, 豈不是在做無(wú)用功?這里使用的方法主要是下面這個(gè):
- (nullable NSURL *)URLForUbiquityContainerIdentifier:(nullable NSString *)containerIdentifier
這個(gè)方法會(huì)返回一個(gè)URL地址, 如果iCloud不可用,返回的將會(huì)是nil,我們以此來(lái)判斷iCloud是否可用.
參數(shù)containerIdentifier:
在我們啟用iCloud的時(shí)候,即Capabilities-->iCloud-->Containers,這里我們選擇的是默認(rèn)的容器, 當(dāng)然也可以點(diǎn)擊" + "來(lái)添加新的容器,然后把這個(gè)新的容器的名字設(shè)置為這個(gè)參數(shù).
一般,一個(gè)APP只要一個(gè)容器就夠了, 這里使用默認(rèn)的即可,不需要新建,所以,這里直接傳nil,即: 找到的第一個(gè)可用的容器即可. 完整的判斷方法為:
+ (BOOL)iCloudEnable {
// 獲得文件管理器
NSFileManager *manager = [NSFileManager defaultManager];
// 判斷iCloud是否可用
// 參數(shù)傳nil表示使用默認(rèn)容器
NSURL *url = [manager URLForUbiquityContainerIdentifier:nil];
// 如果URL不為nil, 則表示可用
if (url != nil) {
return YES;
}
NSLog(@"iCloud 不可用");
return NO;
}
2.2 獲取完整的URL地址(文件在iCloud的保存位置)
在驗(yàn)證iCloud可用之后, 接下來(lái)就要獲取這個(gè)iCloud的保存文件的位置, 就相當(dāng)于本地沙盒的路徑. 其實(shí)上面已經(jīng)獲取了URL地址, 相當(dāng)于本地沙盒的根目錄, 我們一般是把文件保存在Documents文件夾下, iCloud也有個(gè)Documents,只需要把上面的代碼稍作修改即可:
+ (NSURL *)iCloudFilePathByName:(NSString *)name {
NSFileManager *manager = [NSFileManager defaultManager];
// 判斷iCloud是否可用
// 參數(shù)傳nil表示使用默認(rèn)容器
NSURL *url = [manager URLForUbiquityContainerIdentifier:nil];
if (url == nil) {
return nil;
}
url = [url URLByAppendingPathComponent:@"Documents"];
NSURL *iCloudPath = [NSURL URLWithString:name relativeToURL:url];
return iCloudPath;
}
這里的參數(shù)name,就是保存在iCloud時(shí)的文件名稱.
2.3 獲取本地沙盒的路徑
這個(gè)路徑是為后面保存到iCloud的時(shí)候使用的, 保存的方法有一個(gè)參數(shù), 是傳的需要保存文件的URL, 我這里是先將要保存的文件寫(xiě)入本地沙盒( 其實(shí)一般需要備份的文件都是在本地沙盒的 ), 然后再上傳到iCloud:
+ (NSString *)localFilePath:(NSString *)name {
// 得到本程序沙盒路徑
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:name];
return filePath;
}
2.4 保存文件到iCloud
在保存到iCloud之前需要先判斷當(dāng)前的文件在iCloud是否存在:
- (BOOL)isUbiquitousItemAtURL:(NSURL *)url
存在的話,可以直接使用寫(xiě)入文件的方式進(jìn)行將數(shù)據(jù)保存到iCloud:
- (BOOL)writeToURL:(NSURL *)url options:(NSDataWritingOptions)writeOptionsMask error:(NSError **)errorPtr
這里我是轉(zhuǎn)為NSData來(lái)寫(xiě)入的, 其實(shí)還可以使用NSArray, NSDictionary等可以直接歸檔的方式寫(xiě)入. 因?yàn)楹芏啾镜匚募伎梢允褂?strong>NSData ,所以這里就直接使用這個(gè)比較通用的方式來(lái)寫(xiě)入了.
如果iCloud中不存在,就要使用下面的額方法來(lái)寫(xiě)入:
- (BOOL)setUbiquitous:(BOOL)flag itemAtURL:(NSURL *)url destinationURL:(NSURL *)destinationURL error:(NSError **)error
這里主要是url和destinationURL, 前者是iCloud的URL, 后者是本地文件的URL, 也就是上面準(zhǔn)備的.
我這里上傳的操作是這樣的:
// private method
+ (void)uploadToiCloud:(NSString *)name localFile:(NSString *)file resultBlock:(uploadBlock)block {
NSURL *iCloudUrl = [self iCloudFilePathByName:name];
NSString *localFilePath = file;
if ([file componentsSeparatedByString:@"/"].count < 2) {
localFilePath = [self localFilePath:file];
}
NSFileManager *manager = [NSFileManager defaultManager];
// 判斷本地文件是否存在
if ([manager fileExistsAtPath:localFilePath]) {
NSData *data = [NSData dataWithContentsOfFile:localFilePath];
// 判斷iCloud里該文件是否存在
if ([manager isUbiquitousItemAtURL:iCloudUrl]) {
NSError *error = nil;
[data writeToURL:iCloudUrl options:NSDataWritingAtomic error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
block(error);
});
} else {
NSURL *fileUrl = [NSURL fileURLWithPath:localFilePath];
NSError *error = nil;
[manager setUbiquitous:YES itemAtURL:fileUrl destinationURL:iCloudUrl error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
block(error);
});
}
}
}
簡(jiǎn)單的加了一些判斷邏輯, 在進(jìn)行這個(gè)操作的時(shí)候, 最好使用異步, 大家也看到了, 這是一個(gè)私有的方法, 在進(jìn)行這個(gè)操作之前, 我又進(jìn)行了一層的封裝, 只是加了一些判斷:
+ (void)uploadToiCloud:(NSString *)name file:(id)file resultBlock:(uploadBlock)block {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
if ([file isKindOfClass:[NSString class]]) {
[self uploadToiCloud:name localFile:file resultBlock:block];
} else {
NSString *path = [self localFilePath:@"temp.data"];
NSError *error = nil;
if ([file writeToFile:path options:NSDataWritingAtomic error:&error]) {
[self uploadToiCloud:name localFile:path resultBlock:block];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
block(error);
});
}
}
});
}
只是判斷保存的文件如果不是路徑, 就先寫(xiě)入本地, 再使用上面的方法來(lái)上傳, 另外,在這里使用了GCD來(lái)異步執(zhí)行;
2.5 從iCloud同步數(shù)據(jù)到本地
同步到本地之前, 需要先判斷當(dāng)前的文件是否可用進(jìn)行同步, 這里使用了官方提供的一個(gè)方法:
// // 此方法是官方文檔提供,用來(lái)檢查文件狀態(tài)并下載
+ (BOOL)downloadFileIfNotAvailable:(NSURL*)file {
NSNumber* isIniCloud = nil;
if ([file getResourceValue:&isIniCloud forKey:NSURLIsUbiquitousItemKey error:nil]) {
// If the item is in iCloud, see if it is downloaded.
if ([isIniCloud boolValue]) {
NSNumber* isDownloaded = nil;
if ([file getResourceValue:&isDownloaded forKey:NSURLUbiquitousItemDownloadingStatusKey error:nil]) {
if ([isDownloaded boolValue])
return YES;
// Download the file.
NSFileManager* fm = [NSFileManager defaultManager];
if (![fm startDownloadingUbiquitousItemAtURL:file error:nil]) {
return NO;
}
return YES;
}
}
}
// Return YES as long as an explicit download was not started.
return YES;
}
自己需要做的, 就是在解析數(shù)據(jù)之前, 檢查一下這個(gè)狀態(tài), 然后獲取/解析數(shù)據(jù):
+ (void)downloadFromiCloud:(NSString *)name responsBlock:(downloadBlock)block {
NSURL *iCloudUrl = [self iCloudFilePathByName:name];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
if ([self downloadFileIfNotAvailable:iCloudUrl]) {
// 先嘗試轉(zhuǎn)為數(shù)組
NSArray *array = [[NSArray alloc]initWithContentsOfURL:iCloudUrl];
if (array != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
block(array);
});
} else {
// 如果數(shù)組為nil, 再嘗試轉(zhuǎn)為字典
NSDictionary *dic = [[NSDictionary alloc]initWithContentsOfURL:iCloudUrl];
if (dic != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
block(dic);
});
} else {
// 如果字典為nil, 最后嘗試轉(zhuǎn)為NSData
NSData *data = [[NSData alloc]initWithContentsOfURL:iCloudUrl];
if (data != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
block(data);
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
block(nil);
});
}
}
}
} else {
dispatch_async(dispatch_get_main_queue(), ^{
block(nil);
});
}
});
}
我這里使用的方法, 就比較笨了. 其實(shí), 文件上傳時(shí)的類型我們是可控的, 這樣在解析的時(shí)候就會(huì)比較有針對(duì)性, 不用這么一個(gè)個(gè)去檢查判斷.
好了, 以上便是使用NSFileManager 進(jìn)行的iCloud 同步操作, 保存成功與否, 可在手機(jī)"設(shè)置-->iCloud-->儲(chǔ)存空間-->管理儲(chǔ)存空間" 來(lái)查看, 這里列舉了所有已備份到iCloud的APP數(shù)據(jù).
最后附上一個(gè)demo: LZiCloudDemo
里面有兩種用法, 一個(gè)是使用NSFileManager, 一個(gè)是使用UIDocument, 關(guān)于UIDocument, 可參考這篇文章: [iOS]文檔操作之UIDocument