主要講解iCloud工程的創(chuàng)建和CloudKit的使用
CloudKit是什么
- 云端數(shù)據(jù)庫,存儲(chǔ)數(shù)據(jù),提供簡(jiǎn)單的增刪改查功能
- 特點(diǎn)是方便簡(jiǎn)單,適合不懂后臺(tái)的個(gè)人開發(fā)者.
- 安全,畢竟是蘋果自家的產(chǎn)品.不需要復(fù)雜的登錄認(rèn)證體系.
- 目前蘋果允許你使用 CloudKit 存儲(chǔ) 10 GB 資源,100 M 數(shù)據(jù)庫存儲(chǔ),每天 2 GB 流量;當(dāng)你的用戶數(shù)量增加的時(shí)候,這些免費(fèi)額度也相應(yīng)地增加到 1 PB 存儲(chǔ)、10 TB 數(shù)據(jù)庫存儲(chǔ),以及每天 200 TB 流量,幾乎用不完.你感覺不夠用的時(shí)候,你的用戶量已經(jīng)很龐大了....
- 由兩部分組成
- 一個(gè)儀表web頁面,用于管理公開數(shù)據(jù)的記錄類型.
- 一組API接口,用于iCloud和設(shè)備之間的數(shù)據(jù)傳遞.
一、開發(fā)者賬號(hào)中啟用iCloud服務(wù)
1.選擇要添加iCloud功能的appid,勾選iCloud,選擇Include CloudKit support (requires Xcode 6),后點(diǎn)擊Edit,選擇需要額外添加的Container。

2.每個(gè)bundleid下本身會(huì)有一個(gè)Container,如果需要額外的Container,可以通過iCloud Containers創(chuàng)建

3.然后可以在Edit里選擇需要的Container后,點(diǎn)擊continue 以及Assign就完成了添加。

->>

二、在Xcode中啟用iCloud
1.必須在Xcode中添加賬號(hào),選擇對(duì)應(yīng)的Team

2.在Capabilities里,打開iCloud開關(guān),并勾選CloudKit,如果需要額外的容器,勾選Specify custom containers,選擇額外的容器,這個(gè)容器也可以在其他app中添加。

三、關(guān)于 CloudKit Dashboard
1.可以點(diǎn)擊上圖中的CloudKit Dashboard按鈕進(jìn)入,也可以在https://developer.apple.com里進(jìn)入

2.選擇該Team下其中一個(gè)容器


其中主要使用的是Record Types
一個(gè)Record Type用來定義一個(gè)單獨(dú)的記錄(可以理解為一個(gè)數(shù)據(jù)模型),相當(dāng)于存儲(chǔ)數(shù)據(jù)的模板,和數(shù)據(jù)庫的表結(jié)構(gòu)類似
PUBLIC DATA 和 PRIVATE DATA 就是你保存數(shù)據(jù)的地方,開發(fā)者可以查看所有的共享數(shù)據(jù),但是只能看到自己的私密數(shù)據(jù),無法看到用戶的私密數(shù)據(jù);這里沒有顯示PRIBATE DATA,其結(jié)果和PUBLIC DATA是一樣的;
可以在Dashboard中添加記錄、添加記錄模型等


支持的數(shù)據(jù)類型
四、代碼中使用
CloudKit 基礎(chǔ)對(duì)象類型
CloudKit 的基礎(chǔ)對(duì)象類型有 7 種。
- CKContainer: Containers 就像應(yīng)用運(yùn)行的沙盒一樣,一個(gè)應(yīng)用只能訪問自己沙盒中的內(nèi)容而不能訪問其他應(yīng)用的。Containers 就是最外層容器,每個(gè)應(yīng)用有且僅有一個(gè)屬于自己的 container。(事實(shí)上,經(jīng)過開發(fā)者授權(quán)配置 CloudKit Dashboard 之后,一個(gè)應(yīng)用也可以訪問其他應(yīng)用的 container,即共享容器)
- CKDatabase: Database 即數(shù)據(jù)庫,私有數(shù)據(jù)庫用來存儲(chǔ)敏感信息,比如說用戶的性別年齡等,用戶只能訪問自己的私有數(shù)據(jù)庫。應(yīng)用也有一個(gè)公開的數(shù)據(jù)庫來存儲(chǔ)公共信息,例如你在構(gòu)建一個(gè)根據(jù)地理位置簽到的應(yīng)用,那么地理位置信息就應(yīng)該存儲(chǔ)在公共數(shù)據(jù)庫里以便所有用戶都能訪問到。
- CKRecord: 即數(shù)據(jù)庫中的一條數(shù)據(jù)記錄。CloudKit 使用 record 通過 k/v 結(jié)構(gòu)來存儲(chǔ)結(jié)構(gòu)化數(shù)據(jù)。關(guān)于鍵值存儲(chǔ),目前值的架構(gòu)支持 NSString、NSNumber、NSData、NSDate、CLLocation,和 CKReference、CKAsset(這兩個(gè)下面我們會(huì)說明),以及存儲(chǔ)以上數(shù)據(jù)類型的數(shù)組。
- CKRecordZone: Record 不是以零散的方式存在于 database 之中的,它們位于 record zones 里。每個(gè)應(yīng)用都有一個(gè) default record zone,你也可以有自定義的 record zone。
- CKRecordIdentifier: 是一條 record 的唯一標(biāo)識(shí),用于確定該 record 在數(shù)據(jù)庫中的唯一位置。
- CKReference: Reference 很像 RDBMS 中的引用關(guān)系。還是以地理位置簽到應(yīng)用為例,每個(gè)地理位置可以包含很多用戶在該位置的簽到,那么位置與簽到之間就形成了這樣一種包含式的從屬關(guān)系。
- CKAsset: 即資源文件,例如二進(jìn)制文件。還是以簽到應(yīng)用為例,用戶簽到時(shí)可能還包含一張照片,那么這張照片就會(huì)以 asset 形式存儲(chǔ)起來。
1.增加一條記錄
//獲取默認(rèn)的容器
CKContainer *container = [CKContainer defaultContainer];
// 如果是自定義的容器
// CKContainer *shareContainer = [CKContainer containerWithIdentifier:ContainerID];
CKDatabase *database;
if(isPublic)
{
database = container.publicCloudDatabase;//公共數(shù)據(jù)庫
}
else
{
database = container.privateCloudDatabase;//私有數(shù)據(jù)庫
}
//創(chuàng)建主鍵ID 這個(gè)ID到時(shí)查找有用到
CKRecordID *noteId = [[CKRecordID alloc] initWithRecordName:recordID];
//創(chuàng)建CKRecord 保存數(shù)據(jù)
CKRecord *noteRecord = [[CKRecord alloc] initWithRecordType:@"User" recordID:noteId];
//設(shè)置數(shù)據(jù)
[noteRecord setObject:name forKey:@"name"];
[noteRecord setObject:password forKey:@"password"];
//保存操作
[database saveRecord:noteRecord completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"保存成功");
}
else
{
NSLog(@"保存失敗: %@",error);
}
}];
注意 :
默認(rèn)用戶只能只讀數(shù)據(jù)庫,要添加修改則需要登錄icloud賬戶
公有數(shù)據(jù)庫所有的用戶(安裝app的用戶,不是指開發(fā)者)都可以訪問,私有的只能當(dāng)前用戶能訪問.
-
如果用戶沒有登錄,提醒用戶登錄icloud
[[CKContainer defaultContainer] accountStatusWithCompletionHandler:^(CKAccountStatusaccountStatus,NSError *_Nullableerror) { if(accountStatus ==CKAccountStatusNoAccount) { handler(NO); } else { //登錄過了 handler(YES); } }];
2.獲取一條記錄
CKRecordID *noteId = [[CKRecordID alloc]initWithRecordName:recordID];
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
//通過主鍵ID查找記錄
[publicDatabase fetchRecordWithID:noteId completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"查詢成功: %@",record);
}
else
{
NSLog(@"查詢失敗: %@",error);
}
}];
3.查詢多條記錄
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name != %@",@"xiaowang"];
CKQuery *query = [[CKQuery alloc] initWithRecordType:recordTypeName predicate:predicate];
NSSortDescriptor *firstDescriptor = [[NSSortDescriptor alloc] initWithKey:@"gender" ascending:NO];
NSSortDescriptor *secondDescriptor = [[NSSortDescriptor alloc] initWithKey:@"age" ascending:NO];
query.sortDescriptors = @[firstDescriptor,secondDescriptor];
//通過謂詞查找記錄
[publicDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray<CKRecord*> *_Nullableresults,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"results: %@",results);
}
else
{
NSLog(@"查詢失敗: %@",error);
}
}];
4.更新一條記錄
CKRecordID *noteId = [[CKRecordID alloc] initWithRecordName:recordID];
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
//先找到記錄,再修改記錄
[publicDatabase fetchRecordWithID:noteId completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
[recordsetObject:@"123456789" forKey:@"password"];
[recordsetObject:@"m" forKey:@"gender"];
[recordsetObject:@20 forKey:@"age"];
//修改后保存記錄
[database saveRecord:record completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"修改成功 %@",record);
}
else
{
NSLog(@"修改失敗: %@",error);
}
}];
}
else
{
NSLog(@"找不到該記錄,查詢失敗: %@",error);
}
}];
5.刪除一條記錄
CKRecordID *noteId = [[CKRecordID alloc] initWithRecordName:recordID];
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
[publicDatabase deleteRecordWithID:noteId completionHandler:^(CKRecordID *_NullablerecordID,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"刪除成功");
}
else
{
NSLog(@"刪除失敗: %@",error);
}
}];
6.保存大文件
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
CKRecordID *noteId = [[CKRecordID alloc] initWithRecordName:recordID];
CKRecord *noteRecord = [[CKRecord alloc]initWithRecordType:@"User" recordID:noteId];
NSString *path = [[NSBundle mainBundle] pathForResource:@"lcz" ofType:@"jpg"];
CKAsset *asset = [[CKAsset alloc] initWithFileURL:[NSURL fileURLWithPath:path]];
[noteRecord setObject:name forKey:@"name"];
[noteRecord setObject:password forKey:@"password"];
[noteRecord setObject:asset forKey:@"userImage"];
[publicDatabase saveRecord:noteRecord completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"保存成功");
}
else
{
NSLog(@"保存失敗: %@",error);
}
}];
7.添加地理位置
__weak typeof(self) weakSelf = self;
CLGeocoder *geocoder = [CLGeocoder new];
[geocoder geocodeAddressString:@"北京" completionHandler:^(NSArray<CLPlacemark*> *_Nullableplacemarks,NSError *_Nullableerror) {
if(!error)
{
if(placemarks.count>0)
{
CLPlacemark *placemark = placemarks[0];
NSLog(@"%@",placemark.location);
weakSelf.person.location = placemark.location;
[weakSelf saveRecordWithPublic:YES andKey:@"location" andObject:weakSelf.person.location andRecordType:@"Person" andRecordID:@"sunjie2"];
}
}
}];
8.添加引用(外鍵)
- (void)addReferenceWithPublic:(BOOL)isPublic
action:(CKReferenceAction)action
andReferenceKey:(NSString*)key
andSourceRecordID:(NSString*)sourceRecordID
andTargetRecordID:(NSString*)targetRecordID
{
CKRecordID *noteID = [[CKRecordID alloc]initWithRecordName:targetRecordID];
CKReference *reference = [[CKReference alloc]initWithRecordID:noteID action:action];
CKContainer *container = [CKContainer defaultContainer];
CKDatabase *database;
if(isPublic)
{
database = container.publicCloudDatabase;
}
else
{
database = container.privateCloudDatabase;
}
CKRecordID *sourceRecordId = [[CKRecordID alloc]initWithRecordName:sourceRecordID];
[database fetchRecordWithID:sourceRecordId completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
[record setObject:reference forKey:key];
[database saveRecord:record completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"保存成功");
self.person.workN = reference;
}
else
{
NSLog(@"保存失敗: %@",error);
}
}];
}
}];
}
9.查詢引用的記錄
//拿到引用的id
CKRecordID *recordID = reference.recordID;
//根據(jù)id查詢
[database fetchRecordWithID:recordID completionHandler:^(CKRecord *record,NSError *error) {
if(error)
{
//錯(cuò)誤處理
NSLog(@"查詢失敗%@",error);
}
else
{
// 查詢成功
NSLog(@"查詢成功%@",record);
}
}];
10.批操作處理
CKFetchRecordsOperation;
CKModifyRecordsOperation;
CKQueryOperation;
CKDatabaseOperation;
CKModifyBadgeOperation;
CKOperation;
CKSubscriptionOptions;
CKModifySubscriptionsOperation;
CKFetchSubscriptionsOperation;
- (void)fetchOperationWithRecordID:(NSArray<CKRecordID*>*)fetchRecordIDs
{
CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:fetchRecordIDs];
fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record,CKRecordID *recordID,NSError *error) {
if(error)
{
//錯(cuò)誤處理
NSLog(@"查詢失敗%@",error);
}
else
{
// 查詢成功
NSLog(@"查詢成功%@",record);
}
};
fetchRecordsOperation.fetchRecordsCompletionBlock= ^(NSDictionary*recordsByRecordID,NSError*error) {
if(error)
{
//錯(cuò)誤處理
NSLog(@"查詢失敗%@",error);
}
else
{
// 查詢成功
NSLog(@"查詢成功%@",recordsByRecordID);
}
};
fetchRecordsOperation.database = [[CKContainer defaultContainer] publicCloudDatabase];
[fetchRecordsOperation start];
}
11.添加訂閱和通知
options參數(shù)的可能值是:
CKSubscriptionOptionsFiresOnRecordCreation,
CKSubscriptionOptionsFiresOnRecordDeletion,
CKSubscriptionOptionsFiresOnRecordUpdate,
CKSubscriptionOptionsFiresOnce.
因?yàn)閛ptions參數(shù)是一個(gè)位掩碼,可以訂閱的改變的類型的任何組合。
例如,您可以通過CKSubscriptionOptionsFiresOnRecordCreation| CKSubscriptionOptionsFiresOnRecordUpdate作為選項(xiàng):參數(shù)來接收所有新數(shù)據(jù)的通知。
- (void)addSubscriptionAndNotificationsWithRecordID:(NSString*)recordID AndRecordType:(NSString*)recordType
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@",@"Lu"];
//創(chuàng)建一個(gè)訂閱
CKSubscription*subscription = [[CKSubscription alloc]
initWithRecordType:recordType
predicate:predicate
options:CKSubscriptionOptionsFiresOnRecordCreation];
CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
notificationInfo.alertLocalizationKey = @"訂閱新推送";
notificationInfo.shouldBadge=YES;
subscription.notificationInfo= notificationInfo;
CKDatabase *publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
[publicDatabase saveSubscription:subscription
completionHandler:^(CKSubscription *subscription,NSError *error) {
if(!error)
{
NSLog(@"訂閱成功%@",subscription);
}
else
{
NSLog(@"訂閱失敗%@",error);
}
}];
}