八.數據共享

八.數據共享

共享數據場景:git

  • 與其餘應用集成(例如,讓用戶使用Facebook的登錄信息登錄你的應用)。
  • 發佈一系列互補的應用。
  • 將用戶數據從同一的應用移動到有多個特定用途的應用,檢測其是否存在,並在須要時傳遞控制。
  • 在可用的最佳查看器中打開文檔。

8.1 深層連接

iOS9以後用通用連接替換。github

//源應用
-(void)openTargetApp{
	NSURL *url = [NSURL URLWithString:@"com.yourdomain.app://x-callback-url/quote?ticker=GOOG\%start=2014-01-01&end=2015-12-31"];//1.構造URL
	UIApplication *app = [UIApplication sharedApplication];
	if ([app canOpenURL:url]){//2.檢查應用是否被安裝
//棄用openURL		[app openURL:url];//3.啓動目標應用
		[app openURL:url options:[NSDictionary new] completionHandler:^(BOOL success) {
			
		}];
	}
}

//跳轉的應用
//廢棄
//-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
//	return YES;
//}

-(BOOL)application:(UIApplication *)app
		   openURL:(NSURL *)url
		   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options{//4.接受URL目標應用的委託回調
	
	NSString *host = url.host;//5.從URL中提取必要的詳細信息
	NSString *path = url.path;
	NSDictionary *params = [self parseQuery:url.query];
	if ([@"x-cakkvacj-url" isEqualToString:host]){//6.處理URL
		if ([@"quote" isEqualToString:path]){
			[self processQuoteUsingParameters:params];
		}
	}
	return YES;
}
-(NSDictionary *)parseQuery:(NSString *)query{
	NSMutableDictionary *dict = [NSMutableDictionary new];
	if (query){
		//以 & 和 = 做爲分隔符解析
		NSArray *pairs = [query componentsSeparatedByString:@"&"];//8.解析查詢字符串
		for (NSString *pair in pairs){
			NSArray *kv = [pair componentsSeparatedByString:@"="];
			NSString *key = [kv.firstObject stringByRemovingPercentEncoding];
			NSString *value = [kv.lastObject stringByRemovingPercentEncoding];
			[dict setObject:value forKey:key];
		}
	}
	return [NSDictionary dictionaryWithDictionary:dict];
}
-(void)processQuoteUsingParameters:(NSDictionary *)params{
	NSString *ticker = [params objectForKey:@"ticker"];
	NSString *startDate = [params objectForKey:@"start"];
	NSString *endDate = [params objectForKey:@"end"];//9處理提取的值
}
  • 最好使用較短的URL,由於它們的構建速度和解析速度都比較快。
  • 避免基於正則表達式的模式。
  • 優先選擇基於查詢的URL進行標準解析。
  • 不要在URL中放置任何敏感數據。
  • 不要信任任何傳入的數據。始終驗證URL。做爲附加的措施,可讓應用在傳遞URL前對數據進行簽名,並在處理前驗證簽名。
  • 使用sourceApplication來標識源。有一個應用白名單很是有用,能夠始終信任這些數據。

8.2 剪貼板

官方文檔描述:剪貼板試試用於在應用以內或之間交換數據的安全且標準化的機制。許多操做取決於剪貼板,特別是賦值--剪切--粘貼。但你也能夠在其餘狀況下使用剪貼板,例如,在應用之間共享數據時。 剪貼板能夠是公共的或私有的。每一個剪貼板必須有惟一的名稱。 下圖顯示了一個具備兩個項目的剪貼板,每一個項目都有多種格式。正則表達式

  • 具備兩種標準格式(RTF和純文本)內容的文本項。
  • 具備兩種標準格式(JPG和PNG)和一種私有格式(com.yourdomain.app.type)圖片內容的圖片項目,爲特定應用所專用。

輸入圖片說明

共享數據的代碼示例:數組

//使用剪貼板共享數據
-(void)shareToPublicRTFData:(NSData *)rtfData text:(NSString *)text{
	[[UIPasteboard generalPasteboard] setData:rtfData forPasteboardType:(NSString *)kUTTypeRTF];//設置已知類型kUTTypeRTF的二進制數據
	[[UIPasteboard generalPasteboard] setData:text forPasteboardType:kUTTypePlainText];
	
	[UIPasteboard generalPasteboard].string = text;
	[UIPasteboard generalPasteboard].strings = @[text];
}
//假設數據的UTI類型是"com.yourdomain.app.type"
-(void)shareToPublicCustomData:(NSData *) data{
	[[UIPasteboard generalPasteboard] setData:data forPasteboardType:@"com.yourdomain.app.type"];//爲自定義類型設置二進制數據
}
//共享至自定義命名的剪貼板
-(void)sharePrivatelyCustomData:(NSData *)data{
	UIPasteboard *appPasteboard = [UIPasteboard pasteboardWithName:@"myApp" create:YES];//獲取具備給定名稱的剪貼板。若不存在,新建一個
	
	[appPasteboard setData:data forPasteboardType:@"com.yourdomain.app.type"];//設置自定義剪貼板的數據。
}
//從公共的剪貼板讀取
-(NSArray *)readShareStrings{
	return [UIPasteboard generalPasteboard].strings;
}

//從命名的剪貼板讀取
-(NSData *)readPrivateData{
	UIPasteboard *appPasteboard = [UIPasteboard pasteboardWithName:@"myApp" create:YES];
	
	return [appPasteboard dataForPasteboardType:@"com.yourdomain.app.type"];//在自定義剪貼板中檢索自定義類型的數據
}

與深層連接相比,剪貼板優勢:緩存

  • 具備支持複雜數據(如圖像)的能力
  • 支持在多種形式中表示數據,這些形式能夠基於目標應用的功能來選擇。例如,消息應用可使用純文本格式,郵件應用可使用來自同一剪貼板項目的富文本格式。
  • 即便應用關閉後,剪貼板內容仍然會保留。

最佳實踐:安全

  • 剪貼板本質上是由剪貼板服務進行調解的進程間通訊。IPC(Inter-Process Communication)的全部安全規則都適用。https://zh.wikipedia.org/wiki/行程間通訊
  • 由於不能控制哪一個應用會訪問剪貼板,因此使用時老是不安全的,除非數據被加密。
  • 不要在剪貼板中使用大量數據。雖然剪貼板支持交換圖像以及多種格式,但每一個條目不只消耗內存,也須要額外的時間來讀寫。
  • 當應用將使用applicationDidEnterBackgroundNotification通知或applicationWillResignActiveNotification通知進入後臺時,消除剪貼板。更好的作法是,能夠實現UIApplocationDelegate響應的回調方法。經過將item設置爲nil。能夠消除剪貼板,myPasteboard.items = nil;
  • 爲防止任何類型的複製/粘貼,繼承UITextView,並在canPerformAction的copy:動做中返回NO。

8.3 共享內容

8.3.1 文檔交互

UIDocumentInteractionController類容許應用利用設備上的其餘應用打開文檔。支持預覽,打印,郵寄和複印文檔。 UIDocumentInteractionController不是UIViewController的子類,必須配置一個控制器來預覽文檔 控制器的使用涉及兩個方面:發佈者和用戶服務器

輸入圖片說明

1.發佈者app

統一類型標識符:(uniform type identifier,UTI) 是用來惟一標識某一項目類型的文本字符串.

內置的UTI用來標識公共系統對象。例如,文檔的public.document,JPEG圖像的public.jpeg和純文本的public.plain-text.dom

第三方開發人員添加本身的UTI,用於特定應用或專有用途。例如,用於PDF文檔的con.adobe.pdf和用於Apple Keynote文檔的com.apple.keynote.key 編輯器

#import <MobileCoreServices/MobileCoreServices.h>//1.UIDocumentInteraction類,相關類型以及常量定義在MobileCoreServices中
@interface ZJDocumentViewController : UIViewController<UIDocumentInteractionControllerDelegate>//2.所在控制器須要實現協議
@property (nonatomic , strong) UIDocumentInteractionController *docController;
@end

@implementation ZJDocumentViewController

- (void)viewDidLoad {
    [super viewDidLoad];
	
}

#pragma mark document Delegate
-(UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller{//3.此方法提供將要展現子視圖的UIViewController,必須實現
	return self;
}
-(NSURL *)fileInDocsDirectory:(NSString *)filename{
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	NSString *docsDir = [paths firstObject];
	NSString *fullPath = [docsDir stringByAppendingPathComponent:filename];
	return [NSURL fileURLWithPath:fullPath];
}

-(void)configureDIControlWithURL:(NSURL *)url uti:(NSString *) uti{//4.使用URL和UTI類型配置控制器
	UIDocumentInteractionController *controller = [UIDocumentInteractionController interactionControllerWithURL:url];//5獲取URL指向的控制器引用
	controller.delegate = self;
	controller.UTI = uti;//6指定委託和UTI類型
	self.docController = controller;//7設置對控制器的強引用,確保控制器不會過早地被釋放
}

#pragma Action
-(IBAction)previewDocument:(id)sender{//預覽操做
	NSURL *fileURL = [self fileInDocsDirectory:@"sample.pdf"];//8UIDocumentInteractionController對象引用的URL必須是操做系統可訪問的
	
	if (fileURL){
		[self configureDIControlWithURL:fileURL uti:(__bridge NSString *)kUTTypePDF];
		[self.docController presentPreviewAnimat
ed:YES];//預覽文檔
	}
}

-(IBAction)openDocument:(id)sender{//打開PDF操做
	NSURL *fileURL = [self fileInDocsDirectory:@"sample.pdf"];
	if (fileURL){
		[self configureDIControlWithURL:fileURL uti:(__bridge NSString *)kUTTypePDF];
		[self.docController presentOpenInMenuFromRect:self.view.frame inView:self.view animated:YES];//顯示「在。。。。打開」的菜單,並讓用戶選擇某一應用打開文檔
	}
}

UIDocumentInteractionController須要一個NSURL來讀取內容,它必須指向一個使用文件scheme的本地文件。任何其餘scheme將引起異常。

2.消費者 文檔的消費者須要兩個基本步驟:註冊應用支持的文件類型,而後處理文檔內容。 要想註冊應用支持的文件類型,必須在應用的Info.plist中的文檔類型部分配置一下詳細信息。

  • 名稱:想提供的人類可讀的名稱。
  • 類型:標準的統一類標識符之一或自定義UTI。
  • 圖標:須要一個與文檔相關聯的圖標。
  • 屬性:配置其餘文檔類型屬性。

輸入圖片說明

-(BOOL)application:(UIApplication *)app
		   openURL:(NSURL *)url
		   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options{//接受URL目標應用的委託回調
	DDLogDebug("%s src :%@,url:%@", __PRETTY_FUNCTION__,src,url);
	return YES;
}

8.3.2 活動

UIActivityViewController提供了一個統一的服務接口,以便共享和執行與應用中數據有關的操做。 使用UIActivityViewController比使用UIDocumentInteractionController更容易,靈活。UIDocumentInteractionController只容許文件URL,可是用UIActivityViewController能夠共享一下一種或多種類型。

  • NSString
  • NSAttributedString
  • NSURL
  • UIImage
  • ALAsset 這展現由照片應用管理的照片或視頻,能夠與目標應用共享。
  • UIActivityItemSource:符合此協議的對象能夠共享。這有助於建立能夠跨應用共享的自定義對象。
-(void)shareSomeContent{
	NSString *text = @"Text to share";
	NSURL *url = [NSURL URLWithString:@"http://github.com"];
	UIImage *image = [UIImage imageNamed:@"blah"];//1要共享的元素
	
	NSArray *item = @[text,url,image];
	UIActivityViewController *ctrl = [[UIActivityViewController alloc]initWithActivityItems:item applicationActivities:nil];
	ctrl.excludedActivityTypes = @[UIActivityTypePostToFacebook];//排除不容許的活動類型
	[self presentViewController:ctrl animated:YES completion:nil];
	
}

共享鑰匙串:共享鑰匙串是在應用間安全分享數據的另外一種選擇。只有屬於相同羣組的ID,且使用相同證書籤名的應用才能共享數據。 在全部應用中實現單點登陸的惟一方法就是使用共享鑰匙串 要實如今同一發佈者的應用之間共享數據,這是惟一方法,且不須要從用戶正在使用的應用調用其餘應用

8.4 iOS8擴展

添加應用擴展條目:

輸入圖片說明 輸入圖片說明

實現數據共享的:

  • 操做擴展
  • 分享擴展
  • 文檔提供者擴展
  • 應用羣組

iOS8中添加的新類:

  • NSExtensionContext: 表示調用擴展時的主應用上下文。
  • NSExtensionItem: (https://developer.apple.com/documentation/foundation/nsextensionitem): 表示輸入項數組中的某一項。NSExtensionItem對象是一個不可變集合,集合中的值表明了項目的不一樣方向,可經過attachments屬性得到。
  • NSItemProvider: NSExtensionItem對象attachments屬性中找到的數據對象,如文本,圖像,URL。

8.4.1 配置操做擴展和共享擴展

  • 元數據(info.plist):Xcode將NSExtension->NSExtensionAttributes->NSExtensionActivationRule的值設爲TRUEPREDICATE,實質上表示該操做始終可用。可將其改成Dictionary類型,並使用應用擴展鍵提供更細粒度的控制。http://www.bijishequ.com/detail/50781
  • 目標產品名稱:當建立新擴展時,使用產品名稱字段中提供的名稱建立新目標。

8.4.2 操做擴展

當建立操做擴展時,Xcode將建立一下的附加內容:

  • 故事板主界面:當用戶選擇該操做時,故事板UI即將顯示出來
  • ActionViewController類,支持故事板的視圖控制器類

渲染源應用共享圖像的典型代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // Get the item[s] we're handling from the extension context.
    
    // For example, look for an image and place it into an image view.
    // Replace this with something appropriate for the type[s] your extension supports.
    BOOL imageFound = NO;
    for (NSExtensionItem *item in self.extensionContext.inputItems) {//1.掃描全部的擴展項
        for (NSItemProvider *itemProvider in item.attachments) {//2.對於每一個項目而言,掃描全部的附件
            if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {//3.檢查附件是否爲圖像類型
                // This is an image. We'll load it, then place it in our image view.
                __weak UIImageView *imageView = self.imageView;
                [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) {//4若是是,請檢索內容
                    if(image) {
                        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                            [imageView setImage:image];//5由於檢索回調能夠在非主線程上被調用,因此切換上下文,以便使用UIImage內容更新UIImageview
                        }];
                    }
                }];
                
                imageFound = YES;
                break;
            }
        }
        
        if (imageFound) {
            // We only handle one image, so stop looking for more.
            break;
        }
    }
}

8.4.3 共享擴展

建立共享擴展時,Xcode將建立一下附加內容:

  • 故事板主界面
  • ShareViewController類

ShareViewController與如下生命週期事件掛鉤:

  • 內容驗證:調用的第一個方法是isContentValid。使用NSExtensionContext驗證傳入的值,若是數據有效則返回YES,若是無效就返回NO。不管值是什麼,該活動將始終被顯示,若是內容無效,Post按鈕將被禁用。
  • viewDidLoad:該方法在驗證初始內容及加載試圖後被調用。若是須要的話,使用textView屬性訪問UITextView編輯器,從而對文本進行修改。
  • 獲取配置項:視圖加載後,configurationItems方法會被調用來檢索配置項。
  • 內容變化的驗證:每當用戶更改編輯器中的內容時,調用isContentValid。擴展能夠調用validateContent方法來觸發從新驗證,或調用reloadConfigurationItems方法來從新加載配置項。還能夠實現charactersRemaining方法,返回一個非負值,表示剩餘的字符數。
  • 取消通知:didSelectCancel方法。
  • 發佈通知:didSelectPost方法。

8.4.4 文檔提供者擴展

要想讀取共享文檔的內容,請使用UIDocumentPickerViewController。要呈現一個UI來共享文檔,則應該子類化UIDocumentPickerViewController。 使用文檔提供者須要iCloud受權。 輸入圖片說明

1.打開/導入文檔

輸入圖片說明

UIDocumentPickerViewController對象須要配置一下項目:

  • 文檔類型:編輯器應用能夠支持的UTI類型
  • 模式:必須被配置爲Open或Import
  • 委託
//協議UIDocumentPickerDelegate
-(void)clickOpen{
    NSArray *types = @[];// 能夠處理的UTI類型
	UIDocumentPickerViewController *dpvc = [[UIDocumentPickerViewController alloc]initWithDocumentTypes:types inMode:UIDocumentPickerModeImport];
	dpvc.delegate = self;
	[self.navigationController presentViewController:dpvc animated:YES completion:nil];
}

//委託
-(void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url{
	NSData *data = [NSData dataWithContentsOfURL:url];//url是本地文件URL,文檔的內容將複製到應用的tmp/DocumentPickerIncoming文件夾中
	//處理數據,在編輯器中渲染,讓用戶編輯
}
-(void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller{//取消選擇文檔回調
	//獲取能夠展現一條信息,代表用戶沒有選擇文件
}

2.提供文檔 想成爲提供文檔的數據源,須要如下步驟:

  • 建立UI以幫助用戶選擇文檔
  • 將文檔的內容傳遞給編輯器應用
@interface ZJDocumentPickerExtensionViewController : UIDocumentPickerExtensionViewController
@end

@interface ZJDocumentPickerExtensionViewController ()<UITableViewDelegate , UITableViewDataSource>//1.UIDocumentPickerExtensionViewController向用戶提供可用文檔和目標的列表
@property (nonatomic , strong) NSArray *allFiles;
@end

@interface HPEntry //2表示遠程文件條目的模型類
@property (nonatomic , copy) NSString *fileName;
@property (nonatomic , copy) NSString *serverPath;
@property (nonatomic , assign) NSUInteger *size;
@property (nonatomic , copy) NSString *uti;
@property (nonatomic , copy) NSURL *iconURL;
@end

@implementation ZJDocumentPickerExtensionViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //從服務器檢索文件的元數據並更新
	//UITableview是很好的選擇
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
	HPEntry *selected = [self.allFiles objectAtIndex:indexPath.row];
	//若是須要從服務器下載內容 若是文件內容在服務器上,則必須由應用擴展下載。最終的內容必須來自本地URL
	NSURL *localFileURL = [self.documentStorageURL URLByAppendingPathComponent:selected.fileName];//將內容保存到self.documentStorageURL文件夾中
	[self dismissGrantingAccessToURL:localFileURL];//通知操做系統,編輯器應用必須被授予使用該文件的權限
	
}

@end

8.4.5 應用羣組

雖然擴展老是與應用捆綁在一塊兒,但它在本身的進程中運行,並有本身的數據沙箱。

輸入圖片說明

由於應用擴展在本身的沙箱中運行,因此它所以沒法直接訪問由容器應用直接存儲的數據(文檔文件夾,用戶默認數據,緩存文件夾,Core Data,SQLite,等等)

輸入圖片說明 建立一個共享沙箱,容器應用和應用擴展均可以訪問它。應用羣組支持在多個應用之間共享數據---但與共享鑰匙串相似,應用必須使用相同的證書進行簽名。

輸入圖片說明

-(void)sharedDataUsingAppGroups{
	NSString *sharedGroupId = @"group.com.m10v.hperf";//1羣組ID;必須與Capabilities中提供的匹配
	NSUserDefaults *defs = [[NSUserDefaults alloc]initWithSuiteName:sharedGroupId];//2初始化NSUserDefaults
	NSFileManager *fileMgr = [NSFileManager defaultManager];
	NSURL *groupFolder = [fileMgr containerURLForSecurityApplicationGroupIdentifier:sharedGroupId];//3獲取共享文件夾
	
}
相關文章
相關標籤/搜索