一. 基本知識
1. 定義
簡單的說,App Extension 可以讓開發者們擴充自定義的功能和內容到應用程式之外,並在使用者與其他應用程式或系統互動時提供給使用者。
2. 用途
你可以建立一個app extension通過開啟一個特殊的開關。例如
- Share extension: 讓使用者從瀏覽器分享至其他社交軟體中。
- Today widget:為了讓使用者趕上去看喜歡的比賽你可以再通知中心中提供一個小部件顯示比賽時間提醒等資訊。
- 甚至可以建立一個app extension 提供定製的鍵盤讓使用者可以使用它來代替系統的鍵盤。
3. 型別
extension point : 啟用擴充套件的區域稱為擴充套件點,也就是每一個具有特定功能具體的extension。
每個擴充套件點提供了使用說明和API為開發者去建立一個app extension.
下表是蘋果官方提供的所有Extension point列表
4.本質
每一個extension都是一個獨立的二進位制檔案,它獨立於用於釋出它的應用程式。我們必須使用一個app去包含並且釋出你的extension。
二. 主要概念區別 --- 重點必讀
- app : 就是我們正常手機裡的每個應用程式,即Xcode執行後生成的程式。一個app可以包含一個或多個target,每個target將產生一個product.
- app extension : 為了擴充套件特定app的功能並且依賴於一個特定的app的一條程式。
- containing app:一個app包含一個或多個extension稱為containing app。
- target : 在專案中新建一個target來建立app extension.任意一個target指定了應用程式中構建product的設定資訊和檔案。
- host app : 包含app extension 並且能從中開啟它(並不一定非要從此app內部開啟,可以是app內部也可以是例如Today Widget外部控制元件)。
host app : 我們可以把它理解為宿主的App,能夠調起extension的app被稱為host app,比如:Safari app 裡面網頁分享到微信, Safari就是 host app ; widget的host app就是Today。
Xcode Target 介紹
-
我們通過Xcode新建一個target,Xcode會為每一種extension point提供一個模板,每種模板裡會提供特定的原始檔(原始檔中會包含一些示例程式碼)和設定資訊,build這個target將會生成一個指定的二進位制檔案被新增到app’s bundle中。
-
當我們要釋出的APP中包含app extension,開發者需要提交a containg app到 App Store. 當使用者安裝了這個containg app,extensions也會被隨之安裝。如果要開啟app extension功能,需要使用者手動去觸發它,觸發的地方可能是containing app 內部也可能是外部,例如Today widget 這個extension 需要使用者在通知中心點選編輯按鈕去新增,還有比如需要在偏好設定中去管理的extensions等等.
三. App Extension 工作原理
1. 本質
app extension不是一個app, 它是為了實現了一個特定的的任務,不同的extension point定義了不同的任務。
2. 生命週期 (Life Cycle)
因為app extension不是一個app,它的生命週期和app是不同的,在大多情況下,當使用者從app的UI上或者其他活動檢視控制器中選擇開啟extension功能的選項時將會開始執行。
-
開始:A host app 定義了上下文提供給extension, 當它傳送一個請求並且使用者正確相應後開啟extension的生命週期。
-
結束:通常會在完成host app收到的請求後立即終止。
例如,當使用者從Host app 中選擇一張圖片然後長按圖片點選分享按鈕,使用者可以從可分享的列表中進行選擇,選擇後將會完成分享的動作,此時分享的extension將會結束。
四. App Extension 如何和 App 通訊
1. 簡單互動
-
app extension 和 containing app將不能夠直接互動,典型的,containing app可能還沒有開始執行然而它包含的app extension已經在執行了。(例如,一個天氣的app,當你還沒有開啟它時,你可以在Today Widget中看到今天天氣的資訊)
-
在一個典型的請求響應事務中,系統代表的host app開啟app extension, 通過host提供的extension上下文(context)傳遞資料, 這個extension通過介面的展示來執行一些任務,如果適用於extension的目的,返回資料給host.
-
上圖的虛線代表了app extension 和 containing app之間有限的互動。例如Today widget(只有 Today Extension 才支援通過呼叫其他不可以) 通過呼叫 NSExtensionContext類中
openURL:completionHandler:
方法來要求系統去開啟它的containing app.
2. 具體互動
正如下圖的讀寫箭頭所示,任意一個app extension和它的containing app能夠在一個私有的shared container中分享資料。下圖展示了containing app , app extension 和 host app之間完整的互動方式。
注意:系統使用程式間通訊來確保host app和app extension可以一起工作從而實現一個完整的體驗。在你的程式碼中,你不必考慮它們之間潛在的通訊規則,因為你使用了extension point 和 系統提供的上層API。
五. App Extensions 不可用的API
app extension不能夠像containing app一樣直接進行一些動作,它包括如下:
- 不能使用 sharedApplication 物件及其其中的方法。
- 使用NS_EXTENSION_UNAVAILABLE 巨集在標頭檔案中標記的任意API或類似的不可用的巨集或API在一個不可用的框架中。例如,在iOS 8.0中,the HealthKit framework and EventKit UI framework 將不能使用app extension.
- 不能在iOS裝置中使用相機和麥克風(不像其他app extensions,iMessage app可以訪問這些資源,只有它正確的配置 NSCameraUsageDescription and NSMicrophoneUsageDescription Info.plist 檔案中的Key)。
- 不能長期執行後臺任務,該限制的具體細節因平臺而異。(一個app extension能夠使用NSURLSession物件啟動一個上傳或下載並將這些操作的結果返回給containing app)
- 不能使用Air Drop接收資料(一個app extension能夠像一個正常的app一樣通過UIActivityViewController class去使用AirDrop傳送資料)
六. 建立App Extension
1. 選擇Extension Point
每個extension point都針對一個特定的使用者場景。你的第一步是選擇支援你計劃交付功能的extension point.這個選擇將決定你你能夠使用哪些API以及在特定情景下API的使用方式。
在iOS 和 OS X 中支援extension points, NSExtensionPointIdentifier中描述了它們的info.plist 中extension point中識別符號的key.
2. 在Xcode專案中新建target
為你需要使用的app extension 選擇一個合適的extension point, 之後需要新增一個新的target在你的containing app中。使用Xcode template是最簡單的方式來新增一個app extension為你的extension point提供預先配置好的target。
3. 具體步驟
- 在Xcode專案設定資訊的Capabilities中開啟某些extension point必需的開關,注意部分需要在apple developer後臺中將app id中許可權放開。
- 開啟Xcode,上方工具欄選擇File->New->Target.
- 在新彈出的對話方塊中選擇Application Extension中你需要的extension point.
- 完成後將自動新增一個target在你的專案中,並且包含一些示例程式碼。
- 當你build一個extension模板時,如果成功將生成一個以.appex結尾的extension bundle檔案。
4. 注意:
- app extension target 在專案配置build setttings的Architectures選項下必須包含arm64(iOS) 或x86_64(OS X)架構,否則釋出時將被App Store拒絕。一般來說,當你新建一個app extension target 時Xcode將包含適合的64位帶有“Standard architectures” 的architecture。
- 如果containing app target連結時嵌入一個framework,那麼必須包含64位 architecture否則釋出也將被拒。
- 你可以在About 64-Bit Cocoa Touch Apps這篇文章中你將能夠了解更多依靠你的target平臺有關64位development資訊。
七. 檢查App extension預設模板
1. 模板結構
每個app extension模板都包含一些配置檔案如Info.plist,一個view controller class和一個預設的user interface,所有的這些將都由extension point定義。預設的view controller class可以包含你要實現的extension point方法。
2. Info.plist介紹
app extension target的Info.plist檔案可以標識extension point,並制定extension的詳細資訊。這個檔案至少要包含NSExtension這個key和這個extension point指定的含有鍵值對的字典。例如,NSExtensionPointIdentifier key將需要一個值來展示extension point反向DNS的名字,例如com.apple.widget-extension。下面列舉了一些在NSExtension字典中的鍵值對。
- NSExtensionAttributes:extension point特定的一些屬性,如Photo Editing extension中的PHSupportedMediaTypes
- NSExtensionPrincipalClass: 模板建立的view controller class的名字(例如SharingViewController),當一個host app呼叫extension,這個extension point將例項化這個類。
- NSExtensionMainStoryboard: the extension預設的storyboard檔案,通常被命名為MainInterface。
3. 其他設定
除了plist檔案中的設定,預設情況下,模板可能會設定一些功能。每個extension point能夠定義capabilities使extension point支援的任務型別變得有意義。例如,iOS 文件Provider extension包含com.apple.security.application-groups entitlement.
OS X app extension 所有的模板將預設包含App Sandbox 和 com.apple.security.files.user-selected.read-only entitlements。如果需要去做一些其他事情例如訪問網路或者訪問照片,聯絡人等資訊時你需要定義額外的capabilities對於extension。
八. 響應 Host App 的請求
1. 觸發
在使用者接受一個帶有host app請求的app extension時app extension將被開啟。app extension在收到請求後會開啟幫助使用者執行特定任務,具體是完成或者取消任務取決於使用者在UI介面上的動作。例如,Share Extension收到一個host app請求之後通過彈出一個分享的view作為響應。使用者將可以通過這個View上選擇分享的目標或者是取消本次分享。
當一個host app傳送一個請求給app extension,它將指定一個extension context.對於大部分extensions,context最重要的部分是在extension中設定使用者想要的items工作。例如,OS X Share extension 的 context可能包含一個使用者想要傳送的文字選擇資訊。
2. 步驟
-
發請求
一旦Host app 發出請求(通常呼叫
beginRequestWithExtensionContext:
方法),app extension可以使用 extensionContext屬性在主view controller中獲取這個context.子view controllers也能通過連結訪問該屬性。 -
獲取Context
你可以使用 NSExtensionContext類去檢查這個context並且得到它的items。它可以很好地在檢視控制器的loadView方法中後去這個context和Items以便在你的檢視中展示需要的資訊。程式碼如下
NSExtensionContext *myExtensionContext = self.extensionContext;
-
獲取Items
context 物件的inputItems屬性中包含了extension 需要使用的所有items。
NSArray *inputItems = myExtensionContext.inputItems;
每個NSExtensionItem物件包含了該tiem各方面的很多屬性,例如它的title, content text, attachments, and user info.
注意attachments屬性包含了與item關聯的media data陣列。例如,在關聯sharing request的item中,attachments 屬性包含了使用者想要去分享網頁的資訊。
-
完成或取消任務
app extension給使用者一個選擇去完成或取消此次任務。我們可以通過
completeRequestReturningItems:completionHandler:
方法來選擇返回NSExtensionItem物件給host app,或者cancelRequestWithError:
方法,返回一個錯誤程式碼注意:如果app extension呼叫
completeRequestReturningItems:completionHandler:
方法將提供一個completionHandler回撥,系統要求你至少應該暫停app extension。
3.注意
-
在iOS系統中,app extension 可能需要更多時間來完成一個潛在的耗時任務。例如上傳一個網頁的內容。在這種情況下,你可以使用NSURLSession class在後臺進行傳輸。因為後臺傳輸使用一個獨立的程式,傳輸會繼續,作為一個低優先順序的任務,extension 的程式在完成host app請求後應該被終止。
-
儘管我們可以在後臺完成上傳或下載的任務,但是其他後臺任務例如支援VOIP或者後臺播放音樂等在extensions中不能被實現。如果app extension的Info.plist檔案中包含UIBackgroundModes,釋出時將會被拒。
九. 效能優化
1. 設計簡潔的UI
使用者感覺靈活輕便。設計您的extension,以便在一秒鐘內完成目標。啟動速度過慢的擴充套件由系統終止。
原則:簡單,集中完成一個單一的任務。
注意:extension 的圖示要與app保持一致。
2. 記憶體
執行extension的記憶體限制遠小於前臺應用程式的記憶體限制。在這兩個平臺上,系統可能會主動終止extension,因為使用者希望返回主機應用程式中的host app。某些extension可能比其他extension的記憶體限制更低,例如widgets必須特別高效,因為使用者可能同時開啟多個widgets。
3. 執行
app extension 一旦開啟後是一條獨立的程式,不在containing app 主執行迴圈中,如果extension的回撥在主執行迴圈,它可能有一個較差的使用者體驗在另一個extension或app.
注意:app才是系統資源的主要使用者,extension只是輔助。
十. iOS App extension 測試
1. Debug, Profile, and Test Your App Extension
-
你必須為containing app 和 它的app extensions提供各自簽名資訊(即建立不同的apple id但extension的app id 是app的子id).
-
使用Xcode去debug一個app extension就像去degbug其他程式一樣,不同的是,在extension scheme 執行階段,你要指定一個host app作為可執行檔案。指定完成後,Xcode偵錯程式將附加到該extension。
-
當你將Xcode執行的target選擇到你的extension時,在執行時將會彈出選擇一個app去執行,如果你想要每次都選用一個指定的,你需要進行一下的操作
-
編輯scheme,在彈出選單中選擇Run,在右側的Info資訊中將Executable選擇到我們的host app.
2.注意
-
在你build或run app extension之前,確保選擇了正確的extension’s scheme去除錯。如果你直接去除錯containing app scheme,Xcode不會將你的app extension附加上去除非你從containing app 中呼叫它。
-
在Xcode debug控制檯的日誌中,app extension的二進位制檔案可能與CFBundleIdentifier屬性的值相關聯,而不是CFBundleDisplayName屬性的值。
-
如果直接執行containing app在控制檯Log資訊中看不到extension 檔案中列印的資訊。
十一. 過審須知
1. 釋出App時為了過審,containing app必須提供基本功能給使用者,同時不能只有app extensions.
2.為了過審,無論你為containing app選擇哪種目標裝置系列,都必須為您的應用擴充套件程式指定“iPhone / iPad”(有時稱為universal)作為目標裝置系列。
十二. 處理常見場景
1. 打包包含extension程式碼Framework
我們可以建立一個framework在containing app 和app extension之間共享程式碼。
-
我們需要在包含extension的Target build setting中將“Require Only App-Extension-Safe API”設定為YES。如果你不這麼設定,Xcode會提醒你“linking against dylib not safe for use in application extensions”.
-
為了確保你的framework不包含app extensions不可用的API,如果您有一個包含此類API的自定義框架,您可以從containing app中安全地連結到該框架,但不能與該應用程式包含的extensions共享該程式碼。
當配置Xcode的project時,在Copy Files build phase中你必須選擇“Frameworks”作為destination在你的framework中。
Deploying a Containing App to Older Versions of iOS
2. Containing App中分享資料
關係
app extension 和 containing app 之間不能直接訪問彼此的儲存資料的容器。
但我們可以共享資料。
注意:app extension 的target工程不可以直接訪問應用程式沙盒。
共享資料
為了使用資料共享功能,我們需要在當前Target設定中的Capabilities中,開啟App Group開關,並且在apple developer中建立APP Group,並向我們當前target和containing app的app id中開啟並配置app group功能。 Adding an App to an App Group
- APP group : 允許單個開發團隊生成的多個app共享對一個特定的group container訪問。這個容器不適合面向使用者,例如共享快取或資料庫。
配置成功後,app extension和containing app可以使用 NSUserDefaults API共享一個容器來同步需要互動的資料。我們可以使用下面的API
// Create and share access to an NSUserDefaults object
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName: @"com.example.domain.MyShareExtension"];
// Use the shared user defaults object to update the user account
[mySharedDefaults setObject:@"Hello World!" forKey:@"小東邪"];
[shared synchronize];
複製程式碼
可以通過下面的方法從shared container中讀取存入的資料。
- (NSString *)readDataFromNSUserDefaults {
NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"];
NSString *value = [shared valueForKey:@"小東邪"];
 return value;
}
複製程式碼
下圖展示了containing app , extension 與shared container之間的關係。
注意:當我們建立好一個共享容器時,containing app 和 每個app extension 都具有對容器讀寫的許可權,所以我們必須要考慮資料同步的問題。
-
儲存資料的時候必須指明group id;
-
而且要注意NSUserDefaults能夠處理的資料只能是可plist化的物件,詳情見Property List Programming Guide。
-
為了防止出現資料同步問題,不要忘記呼叫[shared synchronize];
3. 訪問網頁
在Share extension 和 Action extension(僅iOS)中,可以讓使用者訪問web內容通過請求Safari執行JavaScript檔案並將結果返回到副檔名。
4. 執行上傳和下載
5. 宣告支援的資料型別
6. 將Containing app 部署到老版本的應用中
3-6的具體步驟可參考蘋果官方文件