
App Extensions出现在iOS 8中,使该系统更加灵活,强大且对用户而言负担得起。 应用程序可以在通知中心中显示为小部件,可以在“照片”中为照片提供过滤器,还可以显示新的系统键盘等等。 同时,保留了用户数据和系统的安全性。 App Extensions工作的功能将在下面讨论。
苹果一直在努力将应用程序彼此隔离。 这是确保用户安全和保护其数据的最佳方法。 每个应用程序在文件系统中的访问权限均受到限制。 通过应用扩展程序,可以与应用程序进行交互,而无需启动它或将其显示在屏幕上。 因此,当用户与其他应用程序或系统交互时,其功能的一部分将对用户可用。
应用程序扩展名是独立于包含应用程序的应用程序-
包含应用程序运行的可执行文件。 它们本身不能仅在包含应用程序的情况下在App Store上发布。 所有App Extension均执行一项特定任务,并且仅取决于其类型,仅绑定到iOS的一个区域。 例如:“自定义键盘扩展名”用于替换标准键盘,“照片编辑扩展名”用于编辑“照片”中的照片。 当前有
25种类型的附加应用信息。
生命扩展应用程序扩展
用户用来启动App Extension的应用程序称为
Host App 。 主机应用启动应用扩展生命周期,并向其发送请求以响应用户操作:

- 用户通过主机应用程序选择应用程序扩展。
- 主机应用发送应用扩展请求。
- iOS在主机应用程序的上下文中启动应用程序扩展,并在它们之间建立通信通道。
- 用户在App Extension中执行操作。
- App Extension可完成来自Host App的请求,执行任务或启动后台进程以完成请求; 完成任务后,结果可以返回到主机应用程序。
- 一旦App Extension执行了其代码,系统就会终止该App Extension。
例如,当使用Facebook Share Extension共享照片中的照片时,Facebook是包含应用程序,而照片是主机应用程序。 在这种情况下,当用户在“共享”菜单中选择它时,“照片”将启动Facebook Share Extension生命周期:

与App Extension互动

- 包含应用-主机应用
不要互相交流。
- 应用程序扩展-主机应用程序
使用IPC进行交互。
- 应用程序扩展-包含应用程序
间接互动。 应用程序组用于数据交换,而嵌入式框架用于常规代码。 您可以使用URL方案从应用程序扩展中启动“包含应用程序”。
通用代码:动态框架
如果包含应用程序和应用程序扩展名使用相同的代码,则应将其放在动态框架中。
例如,照片编辑扩展程序可以与使用包含应用程序中的某些过滤器的自定义照片编辑应用程序关联。 一个好的解决方案是为这些过滤器创建一个动态框架。
为此,添加一个新的
Target并选择
Cocoa Touch Framework :

指定一个名称(例如
ImageFilters ),然后在导航器面板中可以看到一个具有创建的框架名称的新文件夹:

您需要确保框架不使用App Extensions不可用的API:
- 与UIApplication共享。
- 标有不可访问宏的API。
- 相机和麦克风(iMessage Extension除外)。
- 执行冗长的后台任务(此限制的功能因App Extension的类型而异)。
- 使用AirDrop接收数据。
在App Extensions中使用此列表中的任何一个,都会导致在发布到App Store时被拒绝。
在
常规的框架设置中
,您需要选中
“仅允许应用扩展程序API”旁边的框:

在框架代码中,包含应用程序和应用程序扩展中使用的所有类,方法和属性都必须是
public
。 在需要使用框架的任何地方,请执行
import
:
import ImageFilters
数据交换:应用组
包含应用程序和应用程序扩展名在文件系统中有其自己有限的部分,只有它们才能访问它们。 为了使Containing App和App Extension具有具有读写访问权限的公共容器,您需要为其创建一个App Group。
App Group是在
Apple Developer Portal中创建的:

在右上角单击“ +”,在出现的窗口中输入必要的数据:

接下来
继续->注册->完成 。
在包含应用程序的设置中,转到
功能选项卡,激活应用程序组并选择创建的组:

对于应用程序扩展类似:

现在,包含应用程序和应用程序扩展共享一个容器。 接下来,我们将讨论如何对其进行读写。
用户默认值
要交换少量数据,可以使用
UserDefaults
方便,只需指定应用程序组的名称即可:
let sharedDefaults = UserDefaults(suiteName: "group.com.maxial.onemoreapp")
NSFileCoordinator和NSFilePresenter
对于大数据,
NSFileCoordinator
更适合确保读取/写入一致性。 这将避免数据损坏,因为有可能多个进程可以同时访问它们。
获取共享容器的URL,如下所示:
let sharedUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.maxial.onemoreapp")
记录:
fileCoordinator.coordinate(writingItemAt: sharedUrl, options: [], error: nil) { [unowned self] newUrl in do { let data = try NSKeyedArchiver.archivedData(withRootObject: self.object, requiringSecureCoding: false) try data.write(to: newUrl, options: .atomic) } catch { print(error) } }
阅读:
fileCoordinator.coordinate(readingItemAt: sharedUrl, options: [], error: nil) { newUrl in do { let data = try Data(contentsOf: newUrl) if let object = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSString.self, from: data) as String? { self.object = object } } catch { print(error) } }
值得考虑的是
NSFileCoordinator
是同步工作的。 虽然某些文件将被某个进程占用,但其他文件将不得不等待其释放。
如果您希望应用程序扩展知道包含应用程序何时更改数据状态,
NSFilePresenter
使用
NSFilePresenter
。 这是一个协议,其实现可能如下所示:
extension TodayViewController: NSFilePresenter { var presentedItemURL: URL? { let sharedUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.maxial.onemoreapp") return sharedUrl?.appendingPathComponent("Items") } var presentedItemOperationQueue: OperationQueue { return .main } func presentedItemDidChange() { } }
presentedItemOperationQueue
属性返回一个队列,该队列用于更改文件时的回调。 当进程(在本例中为“包含应用程序”)更改数据内容时,将调用
presentedItemDidChange()
方法。 如果直接使用低级写调用进行了更改,则不会调用
presentedItemDidChange()
。 仅
NSFileCoordinator
使用
NSFileCoordinator
更改。
初始化
NSFileCoordinator
对象时,建议您传递
NSFilePresenter
对象,尤其是当它启动任何文件操作时:
let fileCoordinator = NSFileCoordinator(filePresenter: self)
否则,
NSFilePresenter
对象将收到有关这些操作的通知,当在同一线程中工作时,这可能导致死锁。
要开始监视数据状态,您需要使用相应的对象调用
addFilePresenter(_:)
方法:
NSFileCoordinator.addFilePresenter(self)
以后创建的任何
NSFileCoordinator
对象
NSFileCoordinator
自动知道此
NSFilePresenter
对象,并通知其目录中的更改。
要停止监视数据状态,请使用
removeFilePresenter(_:)
:
NSFileCoordinator.removeFilePresenter(self)
核心数据
对于数据共享,您可以使用SQLite以及相应的Core Data。 他们可以管理使用共享数据的流程。 要将核心数据配置为在包含应用程序和应用程序扩展之间共享,请创建
NSPersistentContainer
的子类并覆盖
defaultDirectoryURL
方法,该方法应返回数据存储地址:
class SharedPersistentContainer: NSPersistentContainer { override open class func defaultDirectoryURL() -> URL { var storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.maxial.onemoreapp") storeURL = storeURL?.appendingPathComponent("OneMoreApp.sqlite") return storeURL! } }
在
AppDelegate
更改
persistentContainer
属性。 如果在创建项目时选中“
使用核心数据”复选框,则会自动创建它。 现在,我们将返回
SharedPersistentContainer
类的对象:
lazy var persistentContainer: NSPersistentContainer = { let container = SharedPersistentContainer(name: "OneMoreApp") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container }()
剩下的就是将
.xcdatamodeld添加到App Extension中。 在导航器面板中选择.xcdatamodeld文件。 在“
文件检查器 ”的“
目标成员身份”部分下,选中“应用程序扩展”旁边的框:

因此,Containing App和App Extension将能够读取数据并将数据写入相同的存储并使用相同的模型。
从应用程序扩展启动包含应用程序
当主机应用发送应用扩展请求时,它会提供一个
extensionContext
。 该对象具有一个
open(_:completionHandler:)
方法,您可以使用该方法打开“包含应用程序”。 但是,此方法不适用于所有类型的App Extension。 在iOS上,Today Extension和iMessage Extension支持它。 iMessage Extension只能使用它来打开“包含应用程序”。 如果Today Extension用它打开了另一个应用程序,则可能需要其他验证才能提交到App Store。
要从“应用扩展”中打开应用,您需要在“包含应用”中定义URL方案:

接下来,使用来自应用扩展程序的此图调用
open(_:completionHandler:)
方法:
guard let url = URL(string: "OneMoreAppUrl://") else { return } extensionContext?.open(url, completionHandler: nil)
对于那些调用
open(_:completionHandler:)
方法的App Extensions类型不可用,还有一种方法。 但是,在App Store中签入时,可能会拒绝该应用程序。 该方法的本质是遍历
UIResponder
对象链,直到有一个接受
openURL
调用的
UIApplication
为止:
guard let url = URL(string: "OneMoreAppUrl://") else { return } let selectorOpenURL = sel_registerName("openURL:") var responder: UIResponder? = self while responder != nil { if responder?.responds(to: selectorOpenURL) == true { responder?.perform(selectorOpenURL, with: url) } responder = responder?.next }
未来的应用程序扩展
应用程序扩展为iOS开发带来了很多好处。 逐渐出现了更多类型的App Extension,它们的功能正在发展。 例如,随着iOS 12 SDK的发布,您现在可以与通知中的内容区域进行交互,该区域已经丢失了很长时间。
因此,Apple继续开发此工具,这激发了对其未来的乐观态度。
有用的链接:官方文件在iOS应用和应用扩展之间共享数据iOS 8 App Extension开发技巧