App ExtensionsはiOS 8に登場し、ユーザーにとってシステムをより柔軟で強力かつ手頃な価格にしました。 アプリケーションは、通知センターにウィジェットとして表示したり、写真の写真に独自のフィルターを提供したり、新しいシステムキーボードを表示したりできます。 同時に、ユーザーデータとシステムのセキュリティが維持されました。 App Extensionsの機能の特徴については、以下で説明します。
Appleは常に、アプリケーションを相互に慎重に分離しようとしてきました。 これは、ユーザーの安全を確保し、データを保護するための最良の方法です。 各アプリケーションには、アクセスが制限されたファイルシステム内の個別の場所が与えられます。 App Extensionsを使用すると、アプリケーションを起動したり画面に表示したりすることなく、アプリケーションと対話できます。 したがって、その機能の一部は、ユーザーが他のアプリケーションまたはシステムと対話するときに利用できます。
App Extensionsは、含むアプリケーション( Containing App)とは独立して実行される実行可能ファイルです。 単独では、含まれているアプリでのみ、App Storeに公開できません。 すべてのApp Extensionsは特定のタスクを1つ実行し、タイプに応じてiOSの1つの領域のみに関連付けられます。 例:カスタムキーボード拡張機能は標準キーボードを置き換えるためのもので、写真編集拡張機能は写真内の写真を編集するためのものです。 App Extensionsには現在25種類があります。
Life Extension App Extension
ユーザーがApp Extensionを起動するために使用するアプリケーションはHost Appと呼ばれます。 ホストアプリはApp Extensionライフサイクルを起動し、ユーザーアクションに応答してリクエストを送信します。
- ユーザーは、ホストアプリからApp Extensionを選択します。
- ホストアプリはApp Extensionリクエストを送信します。
- iOSは、ホストアプリのコンテキストでApp Extensionを起動し、それらの間の通信チャネルを確立します。
- ユーザーがApp Extensionでアクションを実行します。
- App Extensionは、ホストアプリからの要求を完了してタスクを実行するか、バックグラウンドプロセスを開始して完了します。 タスクが完了すると、ホストアプリから結果を返すことができます。
- App Extensionがコードを実行すると、システムはこのApp Extensionを終了します。
たとえば、Facebook Share Extensionを使用してPhotosから写真を共有する場合、FacebookはContaining Appであり、PhotosはHost Appです。 この場合、ユーザーが[共有]メニューで選択すると、PhotosはFacebook共有拡張機能のライフサイクルを開始します。
App Extensionとの相互作用
- 含まれるアプリ-ホストアプリ
互いに対話しないでください。
- App Extension-ホストアプリ
IPCを使用して対話します。
- アプリ拡張機能-アプリを含む
間接的な相互作用。 App Groupsはデータ交換に使用され、 Embedded Frameworksは一般的なコードに使用されます。 URL Schemesを使用して、App ExtensionからContaining Appを起動できます 。
汎用コード:動的フレームワーク
Containing AppとApp Extensionが同じコードを使用する場合は、動的フレームワークに配置する必要があります。
たとえば、写真編集拡張機能は、包含アプリケーションの一部のフィルターを使用するカスタム写真編集アプリケーションに関連付けることができます。 良い解決策は、これらのフィルターの動的フレームワークを作成することです。
これを行うには、新しいターゲットを追加して、 Cocoa Touch Frameworkを選択します。
名前( ImageFiltersなど )を指定すると、ナビゲーターパネルに、作成されたフレームワークの名前を持つ新しいフォルダーが表示されます。
フレームワークがApp Extensionsで利用できないAPIを使用しないことを確認する必要があります。
- UIApplicationから共有。
- アクセス不能マクロでマークされたAPI。
- カメラとマイク(iMessage拡張機能を除く)。
- 長いバックグラウンドタスクの実行(この制限の機能はApp Extensionの種類によって異なります)。
- AirDropでデータを取得します。
App Extensionsでこのリストのいずれかを使用すると、App Storeに公開されたときに拒否されます。
[ 全般]のフレームワーク設定で、 [アプリ拡張機能APIのみを許可する]の横にあるチェックボックスをオンにする必要があります。
フレームワークコードでは、Containing AppおよびApp Extensionsで使用されるすべてのクラス、メソッド、およびプロパティは
public
なければなりません。 フレームワークを使用する必要がある場合は、
import
実行します。
import ImageFilters
データ交換:アプリグループ
Containing AppとApp Extensionには、ファイルシステムの独自のセクションがあり、それらにのみアクセスできます。 Containing AppとApp Extensionに読み取りおよび書き込みアクセスの共通コンテナーを持たせるには、それらのアプリケーショングループを作成する必要があります。
App GroupはApple Developer Portalで作成されます:
右上隅にある[+]をクリックし、表示されるウィンドウで必要なデータを入力します。
次に進む->登録->完了
含まれているアプリの設定で、[ 機能 ]タブに移動し、アプリグループをアクティブにして、作成されたグループを選択します。
同様にApp Extensionの場合:
これで、Containing AppとApp Extensionはコンテナーを共有します。 次に、読み取りと書き込みの方法について説明します。
UserDefaults
少量のデータを交換するには、
UserDefaults
を使用すると便利です
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
が同期して動作することを
NSFileCoordinator
価値があります。 一部のファイルは一部のプロセスによって占有されますが、他のファイルは解放されるまで待機する必要があります。
Containing Appがデータの状態を変更するタイミングをApp Extensionに知らせたい場合は、
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()
メソッドは、プロセス(この場合はContaining App)がデータの内容を変更したときに呼び出されます。 低レベルの書き込み呼び出しを使用して直接変更が行われた場合、
presentedItemDidChange()
呼び出されません。
NSFileCoordinator
を使用した変更のみ
NSFileCoordinator
ます。
NSFileCoordinator
オブジェクトを初期化するとき、特に何らかのファイル操作を開始する場合は、
NSFilePresenter
オブジェクトを渡す
NSFileCoordinator
お勧めします。
let fileCoordinator = NSFileCoordinator(filePresenter: self)
そうしないと、
NSFilePresenter
はこれらの操作に関する通知を受け取り、同じスレッドで作業しているときにデッドロックを引き起こす可能性があります。
データの状態の監視を開始するには、対応するオブジェクトで
addFilePresenter(_:)
メソッドを呼び出す必要があります。
NSFileCoordinator.addFilePresenter(self)
後で作成された
NSFileCoordinator
オブジェクト
NSFileCoordinator
、この
NSFilePresenter
オブジェクトについて自動的に認識し、そのディレクトリの変更について通知します。
データステータスの監視を停止するには、
removeFilePresenter(_:)
使用し
removeFilePresenter(_:)
。
NSFileCoordinator.removeFilePresenter(self)
コアデータ
データを共有するには、SQLiteを使用し、それに応じてコアデータを使用できます。 共有データを処理するプロセスを管理できます。 含むアプリとアプリ拡張機能の間で共有されるコアデータを構成するには、
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は、同じストレージに対してデータを読み書きでき、同じモデルを使用できます。
App Extensionからの包含アプリケーションの起動
ホストアプリがApp Extensionリクエストを送信すると、
extensionContext
提供されます。 このオブジェクトには
open(_:completionHandler:)
メソッドがあり、これを使用してContaining Appを開くことができます。 ただし、この方法はすべての種類のApp Extensionで使用できるわけではありません。 iOSでは、Today ExtensionおよびiMessage Extensionによってサポートされています。 iMessage拡張機能は、Containing Appを開くためにのみ使用できます。 Today Extensionで別のアプリケーションを開くと、App Storeに送信するために追加の確認が必要になる場合があります。
App Extensionからアプリケーションを開くには、Containing AppでURLスキームを定義する必要があります。
次に、App Extensionから次の図を使用して
open(_:completionHandler:)
メソッドを呼び出します。
guard let url = URL(string: "OneMoreAppUrl://") else { return } extensionContext?.open(url, completionHandler: nil)
open(_:completionHandler:)
メソッドを呼び出すこれらのタイプのApp Extensionsには使用できませんが、方法もあります。 ただし、App Storeでチェックインすると、アプリケーションが拒否される可能性があります。 メソッドの本質は、
openURL
呼び出しを受け入れる
UIApplication
ができるまで、
UIResponder
オブジェクトのチェーンを
UIResponder
することです。
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 }
今後のアプリ拡張機能
App ExtensionsはiOSの開発に多くをもたらしました。 徐々に、より多くの種類のApp Extensionsが登場し、それらの機能は発展しています。 たとえば、iOS 12 SDKのリリースでは、通知でコンテンツ領域とやり取りできるようになりました。
このように、Appleはこのツールの開発を続けており、そのツールは将来について楽観的な見方を示しています。
便利なリンク:
公式文書
iOSアプリとアプリ拡張機能間でデータを共有する
iOS 8アプリ拡張機能開発のヒント