APP间通讯。
介绍一下,同一个设备上,app之间实现数据共享和通讯的技术方法。
一、app之间的数据共享策略(Sharing File Data Between Applications in Swift/iOS )
先介绍三个东西:
App Extensions (app 扩展)
Shared Keychain Access(共享钥匙链接口)
Custom Pasteboards(用户粘贴板)
1. app Extensions(Widget /Today Extension 都是app拓展的类型)是个iOS8之后出现的功能。
(1)它其实是一个附属进程,显示在通知中心视图中,可以为用户快速执行一些比较简单独立的任务。但是说到共享数据,因为app进程之间默认的安全域(security domains)是不一样的,默认情况下一个app和它的扩展是不能共享相同数据的。为了解决app和其拓展之间的数据共享问题,就需要使用到 App Group。可以理解为一个App Group提供了共享句柄和共享容器,app和它的扩展可以共同使用这个句柄来存储数据。
(2)具体使用大概有这么几个步骤 :
a. 为一个APP和它的扩展创建App Group :group.com.yourdomain.YourAwesomeGroup
b. 将需要共享的数据库放置在共享容器中,一般使用CoreData,
c. 在 App 扩展中使用 Core Data。创建App 扩展的时候Xcode并没有提供CoreData选项框,所以需要手动添加。
d. 共享其他的数据。 在共享容器中使用Core Data的好处是能搞保证数据的同步,避免数据崩溃。
以使用CoreData为例,下面分别是惯常用法和使用App Group情况下的数据路径获取方法:
// 惯常,方法会返会一个文件路径url,权限是 App bundle可持有
- (NSURL *)applicationDocumentsDirectory {
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
// 要共享,使用App Group,则需要一个共享句柄(Share Bundle),方法如下:
- (NSURL *)applicationDocumentsDirectory {
return [[NSFileManager defaultManager]URLForSecurityApplicationGroupIdentifier:@"group.com.yourdomain.YourAwesomeGroup"];
}
(3)注意:如果是大量数据的,使用CoreData无疑是很好的,当时如果你只是想要存储共享很少的数据,比如一些偏好设置,则可以考虑使用NSUserDefaults。自然与惯常的实例化方法是有区别的,下面是演示代码:
// 实例化NSUserdefaults 对象,得到共享接口
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.yourdomain.YourAwesomeGroup"];
// 使用共享的NSUserdefaults对象,更新参数。
[sharedDefaults setObject:anObject forKey:@"anObjectKey"];
let sharedDefaults = NSUserDefaults(suiteName: "group.com.yourdomain.YourAwesomeGroup")
sharedDefaults?.setObject(anObject, forKey: "anObjectKey")
(4)因为数据共享,安全该如何保证呢? 其实不用担心,App Group策略和揭晓来介绍的钥匙链共享接口的安全原理是一样的,他们都是需要实现了与开发者账号的唯一绑定的(每一个工程的target 中有一个plist配置文件<Enabling App Sandbox>,其中的开关设置都需要开发者账号权限),App Group 号或者 ID号 都是唯一的,就像你的开发者账号一样安全,只有持有者可以得到对应的修改权限。
2. Shared Keychain Access
(1)钥匙链存储策略适合一些简短重要的讯息数据,比如站好密码,信用卡号等,当然惯常这些数据只存在一个app中。如果需要将之在一个设备中共享,是不同app可以使用(比如多个应用使用同一个账号登录),那么就可以使用钥匙链组(Keychain Groups)来实现。
(2)具体步骤大致如下:
a. Xcode中打开应用Target下的钥匙链开关(target >> Capabilities >> 打开Keychain Sharing)
b. Xocde出现开发者登录弹框,选择开发者
c. 指定钥匙链组的名字,如(myKeychainGroup1)
d. 组合 App ID前缀+要是组名($(AppIdentifierPrefix)myKeychainGroup1 格式:AB123CDE45.myKeychainGroup1)
e. 可以进行使用了,(主要是共享钥匙链选项成员(shared Keychain item)的Add、Delete和Find操作)
3. Custom Pasteboards(纸板控件UIPasteboard)
(1)剪切板并不只能存放字符串数据,其还可以进行图片数据与网址URL数据的存放。这个剪切板就是UIPasteboard类,开发者也可以直接通过它来操作数据进行应用内或应用间传值。
(2)UIPastebord类有三个初始化方法
a. + (UIPasteboard *)generalPasteboard;
b. + (UIPasteboard *)pasteboardWithName:(NSString *)pasteboardName create:(BOOL)create;
c. + (UIPasteboard *)pasteboardWithUniqueName;
(3)如果用于应用之间的传值,建议使用第二个实例的纸板,不过他们也是存在局限的,对已名字或标记同一的纸板,如果进行了第二次使用,可能就会覆盖上一次的值,这个需要应用中做注意,第三个方法的在其对应的程序退出之后纸板上的内容会被抹掉,也需注意。一般手法可能是UIPasteboard + URL Scheme 通过URL scheme传递UIPasteboard的名称,然后通过UIPasteboard共享数据。
4. app之间数据共享方法不只是以上三种,
· UIDocumentInteractionController
· UIActivityViewController
·Web Service 通过dropbox或者其他第三方的服务来共享数据。
·……
二、app如何知道存在数据共享数据
先介绍点东西,上文有提到
iOS 中的 URL Scheme
通知 Notification
1. 上文中描述数据共享的方法解决的主要问题是找到一个共享容器,然而当共享之后我们需要通知另外一个app让它知晓,这个时候就需要用到 URL Scheme 或者 Notification。
2. 在iOS的SDK中提供了一个非常有意思的功能,它能将iOS的Application同自定义的URL Schema绑定,同时可以通过URL Scheme在浏览器或者是其他应用中启动这个Application。本文主要介绍如何通过URL Scheme的方式启动应用和参数的传递。
3. 通知,使用苹果提供通知技术,告知一个指定的app,并可以传送少许讯息( < 256 byte).
参考文档:
https://www.invasivecode.com/weblog/sharing-data-between-apps-and-their-extenstions/
http://stackoverflow.com/questions/35299044/sharing-file-data-between-applications-in-swift-ios
http://wufawei.com/2013/06/iOS-inter-app-communication/
http://code.tutsplus.com/tutorials/previewing-and-opening-documents-with-uidocumentinteractioncontroller--mobile-15130
http://sspai.com/31500
———————
See the section "Sharing Data with Your Containing App"
Also see "Adding an App to an App Group":
https://github.com/mutualmobile/MMWormhole
Keep coding and making awesome apps.
Vicente
钥匙链内建有一些API,主要使用规则是,通过组名,配合队列(kSecAttrAccessGroup)键值函数(SecItemAdd,SecItemDelete和SecItemCopyMatching)来实现。使用代码展示如下:
Delete a shared Keychain item
let itemKey = "My key"
let itemValue = "My secretive bee "
let keychainAccessGroupName = "AB123CDE45.myKeychainGroup1"
let queryDelete: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey,
kSecAttrAccessGroup as String: keychainAccessGroupName
]
let resultCodeDelete = SecItemDelete(queryDelete as CFDictionaryRef)
if resultCodeDelete != noErr {
print("Error deleting from Keychain: \(resultCodeDelete)")
}
Add a shared Keychain item
guard let valueData = itemValue.dataUsingEncoding(NSUTF8StringEncoding)else {
print("Error saving text to Keychain")
return
}
let queryAdd: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey,
kSecValueData as String: valueData,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
kSecAttrAccessGroup as String: keychainAccessGroupName
]
let resultCode = SecItemAdd(queryAdd as CFDictionaryRef, nil)
if resultCode != noErr {
print("Error saving to Keychain: \(resultCode)")
}
Find a shared Keychain item
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey,
kSecReturnData as String: kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecAttrAccessGroup as String: keychainAccessGroupName
]
var result: AnyObject?
let resultCodeLoad = withUnsafeMutablePointer(&result) {
SecItemCopyMatching(queryLoad, UnsafeMutablePointer())
}
if resultCodeLoad == noErr {
if let result = result as? NSData,
keyValue = NSString(data: result,
encoding: NSUTF8StringEncoding) as? String {
// Found successfully
print(keyValue)
}
} else {
print("Error loading from Keychain: \(resultCodeLoad)")
}