主要講解iCloud工程的創建和CloudKit的使用
CloudKit是什么
- 云端數據庫,存儲數據,提供簡單的增刪改查功能
- 特點是方便簡單,適合不懂后臺的個人開發者.
- 安全,畢竟是蘋果自家的產品.不需要復雜的登錄認證體系.
- 目前蘋果允許你使用 CloudKit 存儲 10 GB 資源,100 M 數據庫存儲,每天 2 GB 流量;當你的用戶數量增加的時候,這些免費額度也相應地增加到 1 PB 存儲、10 TB 數據庫存儲,以及每天 200 TB 流量,幾乎用不完.你感覺不夠用的時候,你的用戶量已經很龐大了....
- 由兩部分組成
- 一個儀表web頁面,用于管理公開數據的記錄類型.
- 一組API接口,用于iCloud和設備之間的數據傳遞.
一、開發者賬號中啟用iCloud服務
1.選擇要添加iCloud功能的appid,勾選iCloud,選擇Include CloudKit support (requires Xcode 6),后點擊Edit,選擇需要額外添加的Container。
2.每個bundleid下本身會有一個Container,如果需要額外的Container,可以通過iCloud Containers創建
3.然后可以在Edit里選擇需要的Container后,點擊continue 以及Assign就完成了添加。
->>
二、在Xcode中啟用iCloud
1.必須在Xcode中添加賬號,選擇對應的Team
2.在Capabilities里,打開iCloud開關,并勾選CloudKit,如果需要額外的容器,勾選Specify custom containers,選擇額外的容器,這個容器也可以在其他app中添加。
三、關于 CloudKit Dashboard
1.可以點擊上圖中的CloudKit Dashboard按鈕進入,也可以在https://developer.apple.com里進入
2.選擇該Team下其中一個容器
其中主要使用的是Record Types
一個Record Type用來定義一個單獨的記錄(可以理解為一個數據模型),相當于存儲數據的模板,和數據庫的表結構類似
PUBLIC DATA 和 PRIVATE DATA 就是你保存數據的地方,開發者可以查看所有的共享數據,但是只能看到自己的私密數據,無法看到用戶的私密數據;這里沒有顯示PRIBATE DATA,其結果和PUBLIC DATA是一樣的;
可以在Dashboard中添加記錄、添加記錄模型等
支持的數據類型
四、代碼中使用
CloudKit 基礎對象類型
CloudKit 的基礎對象類型有 7 種。
- CKContainer: Containers 就像應用運行的沙盒一樣,一個應用只能訪問自己沙盒中的內容而不能訪問其他應用的。Containers 就是最外層容器,每個應用有且僅有一個屬于自己的 container。(事實上,經過開發者授權配置 CloudKit Dashboard 之后,一個應用也可以訪問其他應用的 container,即共享容器)
- CKDatabase: Database 即數據庫,私有數據庫用來存儲敏感信息,比如說用戶的性別年齡等,用戶只能訪問自己的私有數據庫。應用也有一個公開的數據庫來存儲公共信息,例如你在構建一個根據地理位置簽到的應用,那么地理位置信息就應該存儲在公共數據庫里以便所有用戶都能訪問到。
- CKRecord: 即數據庫中的一條數據記錄。CloudKit 使用 record 通過 k/v 結構來存儲結構化數據。關于鍵值存儲,目前值的架構支持 NSString、NSNumber、NSData、NSDate、CLLocation,和 CKReference、CKAsset(這兩個下面我們會說明),以及存儲以上數據類型的數組。
- CKRecordZone: Record 不是以零散的方式存在于 database 之中的,它們位于 record zones 里。每個應用都有一個 default record zone,你也可以有自定義的 record zone。
- CKRecordIdentifier: 是一條 record 的唯一標識,用于確定該 record 在數據庫中的唯一位置。
- CKReference: Reference 很像 RDBMS 中的引用關系。還是以地理位置簽到應用為例,每個地理位置可以包含很多用戶在該位置的簽到,那么位置與簽到之間就形成了這樣一種包含式的從屬關系。
- CKAsset: 即資源文件,例如二進制文件。還是以簽到應用為例,用戶簽到時可能還包含一張照片,那么這張照片就會以 asset 形式存儲起來。
1.增加一條記錄
//獲取默認的容器
CKContainer *container = [CKContainer defaultContainer];
// 如果是自定義的容器
// CKContainer *shareContainer = [CKContainer containerWithIdentifier:ContainerID];
CKDatabase *database;
if(isPublic)
{
database = container.publicCloudDatabase;//公共數據庫
}
else
{
database = container.privateCloudDatabase;//私有數據庫
}
//創建主鍵ID 這個ID到時查找有用到
CKRecordID *noteId = [[CKRecordID alloc] initWithRecordName:recordID];
//創建CKRecord 保存數據
CKRecord *noteRecord = [[CKRecord alloc] initWithRecordType:@"User" recordID:noteId];
//設置數據
[noteRecord setObject:name forKey:@"name"];
[noteRecord setObject:password forKey:@"password"];
//保存操作
[database saveRecord:noteRecord completionHandler:^(CKRecord *_Nullablerecord,NSError *_Nullableerror) {
if(!error)
{
NSLog(@"保存成功");
}
else
{
NSLog(@"保存失敗: %@",error);
}
}];
注意 :
默認用戶只能只讀數據庫,要添加修改則需要登錄icloud賬戶
公有數據庫所有的用戶(安裝app的用戶,不是指開發者)都可以訪問,私有的只能當前用戶能訪問.
-
如果用戶沒有登錄,提醒用戶登錄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;
//根據id查詢
[database fetchRecordWithID:recordID completionHandler:^(CKRecord *record,NSError *error) {
if(error)
{
//錯誤處理
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)
{
//錯誤處理
NSLog(@"查詢失敗%@",error);
}
else
{
// 查詢成功
NSLog(@"查詢成功%@",record);
}
};
fetchRecordsOperation.fetchRecordsCompletionBlock= ^(NSDictionary*recordsByRecordID,NSError*error) {
if(error)
{
//錯誤處理
NSLog(@"查詢失敗%@",error);
}
else
{
// 查詢成功
NSLog(@"查詢成功%@",recordsByRecordID);
}
};
fetchRecordsOperation.database = [[CKContainer defaultContainer] publicCloudDatabase];
[fetchRecordsOperation start];
}
11.添加訂閱和通知
options參數的可能值是:
CKSubscriptionOptionsFiresOnRecordCreation,
CKSubscriptionOptionsFiresOnRecordDeletion,
CKSubscriptionOptionsFiresOnRecordUpdate,
CKSubscriptionOptionsFiresOnce.
因為options參數是一個位掩碼,可以訂閱的改變的類型的任何組合。
例如,您可以通過CKSubscriptionOptionsFiresOnRecordCreation| CKSubscriptionOptionsFiresOnRecordUpdate作為選項:參數來接收所有新數據的通知。
- (void)addSubscriptionAndNotificationsWithRecordID:(NSString*)recordID AndRecordType:(NSString*)recordType
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@",@"Lu"];
//創建一個訂閱
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);
}
}];
}