iOS開發——高級技術&iCloud服務

iCloud服務數組

iCloud 是蘋果提供的雲端服務,用戶能夠將通信錄、備忘錄、郵件、照片、音樂、視頻等備份到雲服務器並在各個蘋果設備間直接進行共享而無需關心數據同步問題,甚至 即便你的設備丟失後在一臺新的設備上也能夠經過Apple ID登陸同步。固然這些內容都是iOS內置的功能,那麼對於開放者如何利用iCloud呢?蘋果已經將雲端存儲功能開放給開發者,利用iCloud開發者 能夠存儲兩類數據:用戶文檔和應用數據、應用配置項。前者主要用於一些用戶文檔、文件的存儲,後者更相似於平常開放中的偏好設置,只是這些配置信息會同步 到雲端。服務器

要進行iCloud開發一樣須要一些準備工做(下面的準備工做主要是針對真機的,模擬器省略Provisioning Profile配置過程):網絡

一、 2步驟仍然是建立App ID啓用iCloud服務、生成對應的配置(Provisioning Profile),這個過程當中Bundle ID可使用通配符(Data Protection、iCloud、Inter-App Audio、Passbook服務在建立App ID時其中的Bundle ID是可使用通配ID的)。app

3.在Xcode中建立項目(假設項目名稱爲「kctest」)並在項目的 Capabilities中找到iCloud並打開。這裏須要注意的就是因爲在此應用中要演示文檔存儲和首選項存儲,所以在Service中勾選 「Key-value storae」和「iCloud Documents」:ide

130914128704266.png

在項目中會自動生成一個」kctest.entitlements」配置文件,這個文檔配置了文檔存儲容器標識、鍵值對存儲容器標識等信息。測試

130914155582078.png

4.不管是真機仍是模擬器都必須在iOS「設置」中找到iCloud設置登陸帳戶,注意這個帳戶沒必要是沙盒測試用戶。ui

A.首先看一下如何進行文檔存儲。文檔存儲主要是使用UIDocument類來完成,這個類提供了新建、修改(其實在API中是覆蓋操做)、查詢文檔、打開文檔、刪除文檔的功能。this

UIDocument 對文檔的新增、修改、刪除、讀取所有基於一個雲端URL來完成(事實上在開發過程當中新增、修改只是一步簡單的保存操做),對於開發者而言沒有本地和雲端之 分,這樣大大簡化了開發過程。這個URL能夠經過NSFileManager的URLForUbiquityContainerIdentifier:方 法獲取,identifier是雲端存儲容器的惟一標識,若是傳入nil則表明第一個容器(事實上這個容器能夠經過前面生成的 「kctest.entiements」中的Ubiquity Container Identifiers來獲取。如上圖能夠看到這是一個數組,能夠配置多個容器,例如咱們的第一個容器標識是 「iCloud.$(CFBundleIdentifier)」,其中$(CFBundleIdentifier)是Bundle ID,那麼根據應用的Bundle ID就能夠得知第一個容器的標識是「iCloud.com.cmjstudio.kctest」。)。下面是經常使用的文檔操做方法:atom

-(void)saveToURL:forSaveOperation:completionHandler::將指定URL的文檔保存到iCloud(能夠是新增或者覆蓋,經過saveOperation參數設定)。url

-(void)openWithCompletionHandler::打開當前文檔。

注意:刪除一個iCloud文檔是使用NSFileManager的removeItemAtURL:error:方法來完成的。

由 於實際開發過程當中數據的存儲和讀取狀況是複雜的,所以UIDocument在設計時並無提供統一的存儲方式來保存數據,而是但願開發者本身繼承 UIDocument類並重寫-(id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError和-(BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError方法來根據不一樣的文檔類型本身來操做數據(contents)。這兩個方法分別在保存文檔 (-(void)saveToURL:forSaveOperation:completionHandler:)和打開文檔 (-(void)openWithCompletionHandler:)時調用。一般在子類中會定義一個屬性A來存儲文檔數據,當保存文檔時,會經過 -(id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError方法將A轉化爲NSData或者NSFileWrapper(UIDocument保存數據的本質就是保存轉化獲得的NSData或 者NSFileWrapper);當打開文檔時,會經過-(BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError方法將雲端下載的NSData或者NSFileWrapper數據轉化爲A對應類型的數據。爲了方便演示下面簡單定義一個繼承自 UIDocument的KCDocument類,在其中定義一個data屬性存儲數據:

複製代碼
 1 //  2 // KCDocument.m  3 // kctest  4 //  5 // Created by Kenshin Cui on 14/4/5.  6 // Copyright (c) 2015年 cmjstudio. All rights reserved.  7 //  8 #import "KCDocument.h"  9 @interface KCDocument() 10 @end 11 @implementation KCDocument 12 #pragma mark - 重寫父類方法 13 /** 14  * 保存時調用 15  * 16  * @param typeName 17  * @param outError 18  * 19  * @return 20 */ 21 -(id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError{ 22 if (self.data) { 23 return [self.data copy]; 24  } 25 return [NSData data]; 26 } 27 /** 28  * 讀取數據時調用 29  * 30  * @param contents 31  * @param typeName 32  * @param outError 33  * 34  * @return 35 */ 36 -(BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError{ 37 self.data=[contents copy]; 38 return true; 39 } 40 @end
複製代碼

 

如 果要加載iCloud中的文檔列表就須要使用另外一個類NSMetadataQuery,一般考慮到網絡的緣由並不會一次性加載全部數據,而利用 NSMetadataQuery並指定searchScopes爲NSMetadataQueryUbiquitousDocumentScope來限制 查找iCloud文檔數據。使用NSMetadataQuery還能夠經過謂詞限制搜索關鍵字等信息,並在搜索完成以後經過通知的形式通知客戶端搜索的情 況。

你們都知道微軟的OneNote雲筆記本軟件,經過它能夠實現多種不一樣設置間的筆記同步,這裏就簡單實現一個基於iCloud服務的 筆 記軟件。在下面的程序中實現筆記的新增、修改、保存、讀取等操做。程序界面大體以下,點擊界面右上方增長按鈕增長一個筆記,點擊某個筆記能夠查看並編輯。

130914175267976.png

在主視圖控制器首先查詢全部iCloud保存的文檔並在查詢通知中遍歷查詢結果保存文檔名稱和建立日期到UITableView展現;其次當用戶點擊了增長按鈕會調用KCDocument完成文檔添加並導航到文檔詳情界面編輯文檔內容。

複製代碼
  1 //  2 // KCMainTableViewController.m  3 // kctest  4 //  5 // Created by Kenshin Cui on 14/4/5.  6 // Copyright (c) 2015年 cmjstudio. All rights reserved.  7 //  8 #import "KCMainTableViewController.h"  9 #import "KCDocument.h"  10 #import "KCDetailViewController.h"  11 #define kContainerIdentifier @"iCloud.com.cmjstudio.kctest" // 容器id,能夠從生產的entitiements文件中查看Ubiquity Container Identifiers(注意其中 的$(CFBundleIdentifier)替換爲BundleID)  12 @interface KCMainTableViewController ()  13 @property (strong,nonatomic) KCDocument *document;//當前選中的管理對象  14 @property (strong,nonatomic) NSMutableDictionary *files; //現有文件名、建立日期集合  15 @property (strong,nonatomic) NSMetadataQuery *dataQuery;//數據查詢對象,用於查詢iCloud文檔  16 @end  17 @implementation KCMainTableViewController  18 #pragma mark - 控制器視圖方法  19 - (void)viewDidLoad {  20  [super viewDidLoad];  21  22  [self loadDocuments];  23 }  24 #pragma mark - UI事件  25 //新建文檔  26 - (IBAction)addDocumentClick:(UIBarButtonItem *)sender {  27 UIAlertController *promptController=[UIAlertController alertControllerWithTitle:@"KCTest" message:@"請輸入筆記名稱" preferredStyle:UIAlertControllerStyleAlert];  28 [promptController addTextFieldWithConfigurationHandler:^(UITextField *textField) {  29 textField.placeholder=@"筆記名稱";  30  }];  31 UIAlertAction *okAction=[UIAlertAction actionWithTitle:@"肯定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {  32 UITextField *textField= promptController.textFields[0];  33  [self addDocument:textField.text];  34  }];  35  [promptController addAction:okAction];  36 UIAlertAction *cancelAction=[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {  37  38  }];  39  [promptController addAction:cancelAction];  40  [self presentViewController:promptController animated:YES completion:nil];  41 }  42 #pragma mark - 導航  43 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {  44 if ([segue.identifier isEqualToString:@"noteDetail"]) {  45 KCDetailViewController *detailController= segue.destinationViewController;  46 detailController.document=self.document;  47  }  48 }  49 #pragma mark - 屬性  50 -(NSMetadataQuery *)dataQuery{  51 if (!_dataQuery) {  52 //建立一個iCloud查詢對象  53 _dataQuery=[[NSMetadataQuery alloc]init];  54 _dataQuery.searchScopes=@[NSMetadataQueryUbiquitousDocumentsScope];  55 //注意查詢狀態是經過通知的形式告訴監聽對象的  56 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(metadataQueryFinish:) name:NSMetadataQueryDidFinishGatheringNotification object:_dataQuery];//數據獲取完成通知  57 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(metadataQueryFinish:) name:NSMetadataQueryDidUpdateNotification object:_dataQuery];//查詢更新通知  58  }  59 return _dataQuery;  60 }  61 #pragma mark - 私有方法  62 /**  63  * 取得雲端存儲文件的地址  64  *  65  * @param fileName 文件名,若是文件名爲nil則從新建立一個url  66  *  67  * @return 文件地址  68 */  69 -(NSURL *)getUbiquityFileURL:(NSString *)fileName{  70 //取得雲端URL基地址(參數中傳入nil則會默認獲取第一個容器)  71 NSURL *url= [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:kContainerIdentifier];  72 //取得Documents目錄  73 url=[url URLByAppendingPathComponent:@"Documents"];  74 //取得最終地址  75 url=[url URLByAppendingPathComponent:fileName];  76 return url;  77 }  78 /**  79  * 添加文檔到iCloud  80  *  81  * @param fileName 文件名稱(不包括後綴)  82 */  83 -(void)addDocument:(NSString *)fileName{  84 //取得保存URL  85 fileName=[NSString stringWithFormat:@"%@.txt",fileName];  86 NSURL *url=[self getUbiquityFileURL:fileName];  87  88 /**  89  建立雲端文檔操做對象  90 */  91 KCDocument *document= [[KCDocument alloc]initWithFileURL:url];  92 [document saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {  93 if (success) {  94 NSLog(@"保存成功.");  95  [self loadDocuments];  96  [self.tableView reloadData];  97 self.document=document;  98 [self performSegueWithIdentifier:@"noteDetail" sender:self];  99 }else{ 100 NSLog(@"保存失敗."); 101  } 102 103  }]; 104 } 105 /** 106  * 加載文檔列表 107 */ 108 -(void)loadDocuments{ 109  [self.dataQuery startQuery]; 110 } 111 /** 112  * 獲取數據完成後的通知執行方法 113  * 114  * @param notification 通知對象 115 */ 116 -(void)metadataQueryFinish:(NSNotification *)notification{ 117 NSLog(@"數據獲取成功!"); 118 NSArray *items=self.dataQuery.results;//查詢結果集 119 self.files=[NSMutableDictionary dictionary]; 120 //變量結果集,存儲文件名稱、建立日期 121 [items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 122 NSMetadataItem *item=obj; 123 NSString *fileName=[item valueForAttribute:NSMetadataItemFSNameKey]; 124 NSDate *date=[item valueForAttribute:NSMetadataItemFSContentChangeDateKey]; 125 NSDateFormatter *dateformate=[[NSDateFormatter alloc]init]; 126 dateformate.dateFormat=@"YY-MM-dd HH:mm"; 127 NSString *dateString= [dateformate stringFromDate:date]; 128  [self.files setObject:dateString forKey:fileName]; 129  }]; 130  [self.tableView reloadData]; 131 } 132 -(void)removeDocument:(NSString *)fileName{ 133 NSURL *url=[self getUbiquityFileURL:fileName]; 134 NSError *error=nil; 135 //刪除文件 136 [[NSFileManager defaultManager] removeItemAtURL:url error:&error]; 137 if (error) { 138 NSLog(@"刪除文檔過程當中發生錯誤,錯誤信息:%@",error.localizedDescription); 139  } 140 [self.files removeObjectForKey:fileName];//從集合中刪除 141 } 142 #pragma mark - Table view data source 143 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 144 return 1; 145 } 146 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 147 return self.files.count; 148 } 149 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 150 static NSString *identtityKey=@"myTableViewCellIdentityKey1"; 151 UITableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:identtityKey]; 152 if(cell==nil){ 153 cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identtityKey]; 154 cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; 155  } 156 NSArray *fileNames=self.files.allKeys; 157 NSString *fileName=fileNames[indexPath.row]; 158 cell.textLabel.text=fileName; 159 cell.detailTextLabel.text=[self.files valueForKey:fileName]; 160 return cell; 161 } 162 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { 163 if (editingStyle == UITableViewCellEditingStyleDelete) { 164 UITableViewCell *cell=[self.tableView cellForRowAtIndexPath:indexPath]; 165  [self removeDocument:cell.textLabel.text]; 166  [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; 167 } else if (editingStyle == UITableViewCellEditingStyleInsert) { 168 // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view 169  } 170 } 171 #pragma mark - UITableView 代理方法 172 -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ 173 UITableViewCell *cell=[self.tableView cellForRowAtIndexPath:indexPath]; 174 NSURL *url=[self getUbiquityFileURL:cell.textLabel.text]; 175 self.document=[[KCDocument alloc]initWithFileURL:url]; 176 [self performSegueWithIdentifier:@"noteDetail" sender:self]; 177 } 178 @end
複製代碼

 

當新增一個筆記或選擇一個已存在的筆記後能夠查看、保存筆記內容。

複製代碼
 1 /
 2 // ViewController.m  3 // kctest  4 //  5 // Created by Kenshin Cui on 14/4/5.  6 // Copyright (c) 2015年 cmjstudio. All rights reserved.  7 //  8 #import "KCDetailViewController.h"  9 #import "KCDocument.h" 10 #define kSettingAutoSave @"com.cmjstudio.kctest.settings.autosave" 11 @interface KCDetailViewController () 12 @property (weak, nonatomic) IBOutlet UITextView *textView; 13 @end 14 @implementation KCDetailViewController 15 #pragma mark - 控制器視圖方法 16 - (void)viewDidLoad { 17  [super viewDidLoad]; 18  [self setupUI]; 19 } 20 -(void)viewWillDisappear:(BOOL)animated{ 21  [super viewWillDisappear:animated]; 22 //根據首選項來肯定離開當前控制器視圖是否自動保存 23 BOOL autoSave=[[NSUbiquitousKeyValueStore defaultStore] boolForKey:kSettingAutoSave]; 24 if (autoSave) { 25  [self saveDocument]; 26  } 27 } 28 #pragma mark - 私有方法 29 -(void)setupUI{ 30 UIBarButtonItem *rightButtonItem=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(saveDocument)]; 31 self.navigationItem.rightBarButtonItem=rightButtonItem; 32 33 if (self.document) { 34 //打開文檔,讀取文檔 35 [self.document openWithCompletionHandler:^(BOOL success) { 36 if(success){ 37 NSLog(@"讀取數據成功."); 38 NSString *dataText=[[NSString alloc]initWithData:self.document.data encoding:NSUTF8StringEncoding]; 39 self.textView.text=dataText; 40 }else{ 41 NSLog(@"讀取數據失敗."); 42  } 43  }]; 44  } 45 } 46 /** 47  * 保存文檔 48 */ 49 -(void)saveDocument{ 50 if (self.document) { 51 NSString *dataText=self.textView.text; 52 NSData *data=[dataText dataUsingEncoding:NSUTF8StringEncoding]; 53 self.document.data=data; 54 [self.document saveToURL:self.document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) { 55 NSLog(@"保存成功!"); 56  }]; 57  } 58 } 59 @end
複製代碼

 

到 目前爲止都是關於如何使用iCloud來保存文檔的內容,上面也提到過還可使用iCloud來保存首選項,這在不少狀況下一般頗有用,特別是對於開發了 iPhone版又開發了iPad版的應用,若是用戶在一臺設備上進行了首選項配置以後到另外一臺設備上也能使用是多麼優秀的體驗啊。相比文檔存儲,首選項存 儲要簡單的多,在上面「kctest.entitlements」中能夠看到首選項配置並不是像文檔同樣能夠包含多個容器,這裏只有一個Key-Value Store,一般使用NSUbiquitousKeyValueStore的defaultStore來獲取,它的使用方法和 NSUserDefaults幾乎徹底同樣,當鍵值對存儲發生變化後能夠經過 NSUbiquitousKeyValueStoreDidChangeExternallyNotification等得到對應的通知。在上面的筆記應 用中有一個」設置「按鈕用於設置退出筆記詳情視圖後是否自動保存,這個選項就是經過iCloud的首選項來存儲的。

複製代碼
 1 // KCSettingTableViewController.m  2 // kctest  3 //  4 // Created by Kenshin Cui on 14/4/5.  5 // Copyright (c) 2015年 cmjstudio. All rights reserved.  6 //  7 #import "KCSettingTableViewController.h"  8 #define kSettingAutoSave @"com.cmjstudio.kctest.settings.autosave"  9 @interface KCSettingTableViewController () 10 @property (weak, nonatomic) IBOutlet UISwitch *autoSaveSetting; 11 @end 12 @implementation KCSettingTableViewController 13 - (void)viewDidLoad { 14  [super viewDidLoad]; 15  [self setupUI]; 16 } 17 #pragma mark - UI事件 18 - (IBAction)autoSaveClick:(UISwitch *)sender { 19  [self setSetting:sender.on]; 20 } 21 #pragma mark - 私有方法 22 -(void)setupUI{ 23 //設置iCloud中的首選項值 24 NSUbiquitousKeyValueStore *defaults=[NSUbiquitousKeyValueStore defaultStore]; 25 self.autoSaveSetting.on= [defaults boolForKey:kSettingAutoSave]; 26 //添加存儲變化通知 27  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( 28 keyValueStoreChange:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:defaults]; 29 } 30 /** 31  * key-value store發生變化或存儲空間不足 32  * 33  * @param notification 通知對象 34 */ 35 -(void)keyValueStoreChange:(NSNotification *)notification{ 36 NSLog(@"Key-value store change..."); 37 } 38 /** 39  * 設置首選項 40  * 41  * @param value 是否自動保存 42 */ 43 -(void)setSetting:(BOOL)value{ 44 //iCloud首選項設置 45 NSUbiquitousKeyValueStore *defaults=[NSUbiquitousKeyValueStore defaultStore]; 46  [defaults setBool:value forKey:kSettingAutoSave]; 47 [defaults synchronize];//同步 48 } 49 @end
複製代碼

 

運行效果:

222.gif

注意:全部的存儲到iCloud的文檔、首選項都是首先存儲到本地,而後經過daemon進程同步到iCloud的,保存、讀取的文件都是本地同步副本並不必定是真實的iCloud存儲文件。

相關文章
相關標籤/搜索