iCloud開發
- 使用iCloud的開發的前提是要有開發者賬號,個人或企業均可。
iCloud三種型別的儲存方式
型別 | 說明 |
---|---|
key-value storage | 鍵值對的儲存服務,用於一些簡單的資料儲存 |
iCloud Documents | 文件儲存服務,用於將檔案儲存到iCloud中 |
CloudKit | 雲端資料庫服務 |
專案配置
- 這些是基本配置,三種方式都需要這些配置。
1、iCloud 官網配置
-
建立支援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 型別
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(@"下載失敗");
}
}
}];