[譯]AppExtension編程指南:擴展基礎4

App Extension Programming Guide-App Extension Essentials AppExtension編程指南:擴展基礎4html

Handling Common Scenarios 常見問題的處理方案ios

iOS8/OS X v10.10web

常見問題的處理方案

當編寫自定義代碼以執行app擴展任務時,你可能須要處理一些其餘多種類型擴展也會出現的狀況。在這一章節中,咱們將幫助你如何應對和處理這些常見的問題。編程

使用內嵌框架共享代碼

你能夠建立一個內嵌框架,用於在應用擴展和它的主應用程序(containing app)之間共享代碼。好比,你在照片編輯擴展中開發了圖片濾鏡功能,那麼同時該擴展的主應用程序containing app也有這個功能,那麼你能夠將實現該功能的代碼封裝成一個框架,並在擴展target和主應用程序target中嵌入這個框架。swift

你要確保你建立的內嵌框架不包含應用擴展不能使用的API。這類API通常使用unavailability宏來標記,好比像 NS_EXTENSION_UNAVAILABLE數組

若是你建立的內嵌框架中包含應用擴展不能使用的API,你可將其安全地Link到containing app,它能夠正常使用框架中的API,可是不能與應用擴展共享代碼(譯者注:也就是應用擴展不能使用該框架提供的全部API,繼而沒法作到代碼共享)。若是你上傳App Store的應用擴展中有這種框架,或者其餘部分使用了不可用的API,那麼審覈時會被拒絕。瀏覽器

若是咱們要想應用擴展使用內嵌框架,那麼首先要配置一下。將target的Require Only App-Extension-Safe API選項設置爲Yes。若是你不這樣設置,那麼Xcode會向你提示警告:linking against dylib not safe for use in application extensions安全

重要提示:若是containing app要連接至內嵌框架,那麼必需要支持arm64架構,不然在上傳App Store時會被拒絕。(如「建立應用擴展」章節中介紹的,全部應用擴展都要支持arm64架構。)bash

在配置配置Xcode項目時,必須在Copy Files編譯階段選擇「Frameworks」做爲內嵌框架的目標。網絡

重要提示:咱們一般要選擇 Frameworks 做爲 Copy Files 編譯階段目標。若是你將其設置爲 SharedFramework,那麼上傳App Store時會被拒絕的。

你可讓containing app支持iOS7或更早的版本,但當在iOS8或更新的版本中運行時,要特別注意內嵌框架的安全性。詳細內容能夠參閱 Deploying a Containing App to Older Versions of iOS。

有關建立和使用內嵌框架的更多內容,請觀看WWDC 2014的視頻「Building Modern Frameworks」

與Containing App共享數據

應用擴展和它的containing app的安全域是有區別的。即使擴展包是嵌套在containing app包中的。默認狀況下,應用擴展和containing app是不能直接訪問對方的容器的。

BACKGROUND 要了解容器,閱讀 About the iOS File System 中的 File System Programming Guid.

不過你能夠經過數據共享來實現這個願望。好比,你但願應用擴展和它的containing app共享一個單一的大數據集。好比prerendered assets。

要實現數據共享,咱們要使用Xcode或者開發者門戶網站容許應用擴展和它的containing app成爲一個應用組,而後在開發者門戶網站中註冊應用組,並指明在containing app中使用該應用組。關於應用組的知識請查閱 Entitlement Key Reference 文檔的 Adding an App to an App Group 章節。

當你設置好應用組後,應用擴展和它的containing app就能夠經過 NSUserDefaults API共享訪問用戶的信息。咱們可使用 initWithSuiteName: 方法實例化一個 NSUserDefaults 對象,而後傳入共享組的標示符。好比一個共享擴展,它或許會更新用戶最近常用的共享帳號,那麼咱們能夠這樣來寫:

// 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's account. [mySharedDefaults setObject:theAccountName forKey:@"lastAccountName"]; 
複製代碼

下圖向咱們展現了應用擴展和它的containing app是如何經過共享容器實現數據共享的.

Figure 4-1應用擴展的容器與其containing app的容器是不一樣的。

image

重要提示:若是你的應用擴展使用NSURLSession類執行後臺的上傳下載任務時,你必需要設置一個共享容器,這樣擴展和containing app就能夠訪問到轉換傳輸的數據。後臺上傳下載的更多知識請參閱 Performing Uploads and Downloads。

若是你設置了共享容器,那麼containing app和它包含的容許參與數據分享的擴展就能夠對共享容器裏的內容進行讀寫操做了。同時你還必需要對數據的操做進行同步,以免數據損壞或出錯。使用UIDocument類、Core Data或者SQLite能夠幫你可讓用戶經過要求Safari運行JS文件來訪問網絡內容,並將結果返回到擴展。

版本說明 在iOS 8.2及更高版本中,您也可使用UIDocument該類來協調共享數據訪問。 在iOS 9及更高版本中,您能夠NSFileCoordinator直接使用該類進行共享數據訪問,可是若是您這樣作,則必須NSFilePresenter在應用擴輾轉換爲後臺時刪除對象。

訪問網頁

在分享擴展(iOS與OS X平臺)和Action擴展(iOS平臺)中,通常都容許用戶使用Safari瀏覽器訪問網頁並經過執行JavaScript腳本,並將結果返回到擴展中。你也能夠在你的擴展運行以前(適用於兩個平臺)或執行完任務以後(僅適用於iOS平臺)經過JavaScript文件修改網頁內容。好比分享擴展,它能夠幫助用戶分享網頁上的內容,或者iOS上的Action擴展可能會顯示當前網頁的指定翻譯內容。

若是想添加網頁訪問和操做應用擴展,那麼須要遵循下面幾個步驟: 1.建立一個JavaScript文件,並申明一個全局對象,命名爲 ExtensionPreprocessingJS,併爲該對象分配一個新的自定義JavaScript類的實例。 2.在應用擴展的屬性列表文件中添加關鍵字 NSExtensionJavaScriptPreprocessingFile,給 Safari 瀏覽器指明使用哪一個 JavaScript 文件。 3.在NSExtensionActivationRule字典中,將NSExtensionActivationSupportsWebURLWithMaxCount 賦值一個非零的值。(更多關於 NSExtensionActivationRule 字典的知識請參閱 Declaring Supported Data Types for a Share or Action Extension。) 4.當你的應用擴展開始運行時,使用NSItemProvider類得到運行JavaScript文件所返回的結果。 5.在iOS系統的應用擴展中,若是你但願Safari在擴展執行完任務後更新網頁,那麼你要向JavaScript文件中傳入值。(在這一步中也使用NSItemProvider類。)

爲了告知Safari你的應用擴展中包含一個JavaScript文件,你須要在應用擴展的Info.plist文件中,向NSExtensionAttributes字典添加NSExtensionJavaScriptPreprocessingFile關鍵字來指明你的JavaScript文件。這個鍵的值就是你但願當你的應用擴展運行前,Safari要加載的JavaScript文件的名稱。好比:

<key>NSExtensionAttributes</key>
        <dict>
            <key>NSExtensionJavaScriptPreprocessingFile</key>
            <string>MyJavaScriptFile</string> <!-- Do not include the ".js" filename extension -->
        </dict>

複製代碼

在iOS和OS X平臺中,在你自定義的JavaScript類中能夠定義一個run()函數,該函數就是Safari加載JavaScript文件的入口。在run()函數中,Safari提供了一個名爲completionFunction的參數,你可使用鍵值對象的形式將結果傳給應用擴展。

在iOS平臺中,你還能夠定義一個finalize()函數,當應用擴展在任務結束階段調用completeRequestReturningItems:expirationHandler:completion:方法時Safari會調用finalize()函數。在該函數中,能夠經過向completeRequestReturningItems:expirationHandler:completion:方法傳值,來改變網頁內容。

好比,你的iOS應用擴展須要基於一個網頁URI啓動,而且當它結束運行時改變網頁的背景色,那麼你須要這樣寫JavaScript代碼:

清單4-1示例run()和finalize()函數

var MyExtensionJavaScriptClass = function() {};
 
MyExtensionJavaScriptClass.prototype = {
    run: function(arguments) {
    // Pass the baseURI of the webpage to the extension.
        arguments.completionFunction({"baseURI": document.baseURI});
    },
 
// Note that the finalize function is only available in iOS.
    finalize: function(arguments) {
        // arguments contains the value the extension provides in [NSExtensionContext completeRequestReturningItems:completion:].
    // In this example, the extension provides a color as a returning item.
    document.body.style.backgroundColor = arguments["bgColor"];
    }
};
 
// The JavaScript file must contain a global object named "ExtensionPreprocessingJS".
var ExtensionPreprocessingJS = new MyExtensionJavaScriptClass;
複製代碼

在iOS和OS X平臺中,你須要編寫代碼來處理run()函數返回的值,爲獲取到字典中的值,咱們須要指定kUTTypePropertyList類型做爲標示符傳入NSItemProvider類的 loadItemForTypeIdentifier:options:completionHandler:方法。在該字典中使用 NSExtensionJavaScriptPreprocessingResultsKey做爲key來取值。好比下面例子中咱們想要獲取將 URI 傳入 run()的返回值:

[imageProvider loadItemForTypeIdentifier:kUTTypePropertyList options:nil completionHandler:^(NSDictionary *item, NSError *error) {
    NSDictionary *results = (NSDictionary *)item;
        NSString *baseURI = [[results objectForKey:NSExtensionJavaScriptPreprocessingResultsKey] objectForKey:@"baseURI"];
    }];
複製代碼

finalize() 函數是在當應用擴展執行完任務後傳參並調用的,建立一個含有咱們須要處理的值的字典,而後用NSItemProviderinitWithItem:typeIdentifier:方法來封裝該字典。好比當擴展執行完任務後咱們想讓網頁變爲紅色,咱們能夠這樣寫:

NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init];
    extensionItem.attachments = @[[[NSItemProvider alloc] initWithItem: @{NSExtensionJavaScriptFinalizeArgumentKey: @{@"bgColor":@"red"}} typeIdentifier:(NSString *)kUTTypePropertyList]];
    [[self extensionContext] completeRequestReturningItems:@[extensionItem] completion:nil];
 
複製代碼

執行上傳下載任務

用戶通常的操做習慣都傾向於當使用你的應用擴展完成某個任務後,能夠將結果當即反饋在使用擴展的應用中。若是一個擴展要處理的任務包含較長時間的上傳下載操做時,你要確保當你的應用擴展關閉後能繼續完成該任務。爲實現這個功能,咱們須要使用NSURLSession類建立一個URL會話並建立後臺的上傳下載任務。

提示:你能夠回想一下其餘類型的後臺任務,好比後臺支持VoIP、後臺播放音樂,這些是不能用應用擴展去實現的。更多信息請參閱Respond to the Host App’s Request。

當你的應用擴展準備好上傳下載任務後,擴展會完成調用它的應用發出的請求,並在不影響上傳下載任務的前提下終止擴展。更多關於擴展處理載體應用請求的知識請參閱Respond to the Host App’s Request。在iOS系統中,若是你的應用擴展在執行完後臺任務時並無在運行,那麼系統會自動在後臺運行擴展的載體應用,並調用application:handleEventsForBackgroundURLSession:completionHandler: 代理方法。

重要提示:若是你的應用擴展在後臺建立了 NSURLSession 任務,那麼你必需要設置一個共享容器,以確保擴展和載體應用實現數據共享。咱們能夠在 NSURLSessionConfiguration 類中使用sharedContainerIdentifier屬性來指定一個共享容器的標示符,而後咱們就能夠經過該標示符獲取到共享容器。請參閱 Sharing Data with Your Containing App 文檔來設置共享容器。

下面的例子展現瞭如何配置一個URL會話,並建立一個下載任務:

NSURLSession *mySession = [self configureMySession];
NSURL *url = [NSURL URLWithString:@"http://www.example.com/LargeFile.zip"];
NSURLSessionTask *myTask = [mySession downloadTaskWithURL:url];
[myTask resume];
 
- (NSURLSession *) configureMySession {
    if (!mySession) {
        NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@「com.mycompany.myapp.backgroundsession」];
// To access the shared container you set up, use the sharedContainerIdentifier property on your configuration object.
config.sharedContainerIdentifier = @「com.mycompany.myappgroupidentifier」;
        mySession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
    }
    return mySession;
}

複製代碼

由於在單位時間內只能由一個進程使用後臺會話,因此你須要爲載體應用中的全部擴展建立不一樣的後臺會話(每一個後臺會話都要有一個惟一的標示符)。在這裏咱們建議當載體應用在後臺處理擴展的任務時,只使用一個該擴展建立的後臺會話。若是你要執行其餘的網絡相關的任務,那麼就要建立相應的URL會話。

若是你須要在後臺建立URL會話以前完成載體應用的請求,那麼要確保建立和使用會話的代碼是有效可執行的。當你的擴展調用 completeRequestReturningItems:completionHandler: 方法告知主叫應用已經完成相關請求後,系統就能夠隨時終止你的應用擴展。

爲分享和Action擴展申明支持的數據類型

在你的分享或Action擴展中,在它們的工做中可能會使用到一些數據,而且這些數據的類型各不相同。爲了確保只有當用戶在載體應用中選擇了你的擴展支持的數據類型時,纔會展現你的擴展功能。你須要在擴展的Info.plist屬性列表文件中添加 NSExtensionActivationRule 關鍵字。你也可使用該關鍵字指定擴展處理每種類型的最大數目。當你的應用擴展運行時,系統會將NSExtensionActivationRule鍵的值與擴展項的attachments屬性中的信息進行比較。關於 NSExtensionActivationRule 關鍵字的詳細信息能夠參閱 Action Extension Keys文檔中的 Information Property List Key Reference 章節。

好比,你能夠申明你的分享擴展支持最多處理10張圖片,一部影片和一個網站URL。您可使用如下字典做爲該NSExtensionAttributes鍵的值:

<key>NSExtensionAttributes</key>
    <dict>
        <key>NSExtensionActivationRule</key>
        <dict>
            <key>NSExtensionActivationSupportsImageWithMaxCount</key>
            <integer>10</integer>
            <key>NSExtensionActivationSupportsMovieWithMaxCount</key>
            <integer>1</integer>
            <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
            <integer>1</integer>
        </dict>
    </dict>              
複製代碼

若是你想指定不支持的數據類型,那麼你能夠將該類型的值設置爲0,或者在 NSExtensionActivationRule 中不添加該類型便可。

注意:若是你的分享擴展或iOS中的Action擴展須要訪問網頁,那你必需要確保 NSExtensionActivationSupportsWebURLWithMaxCount 關鍵字的值不爲0(更多關於在應用擴展中經過JavaScript訪問網頁的內容請參閱Accessing a Webpage

你也可使用 NSExtensionItem 定義的 UTI子 類型以便數據檢測器檢測文本信息,好比電話號碼或通信地址。

NSExtensionActivationRule字典中的鍵足以知足大多數應用的過濾需求。若是你須要作更復雜的過濾,好比像 public.urlpublic.image 之間的區別,那麼你就得在文本中建立斷言語句。若是你要建立一個斷言,那麼就將NSExtensionActivationRule關鍵字的值設置爲你指定的斷言字符串。(在運行時,系統會自動將該字符串編譯爲 NSPredicate 對象

好比,一個應用擴展的附件屬性能夠指定爲PDF文件,能夠這樣寫:

{extensionItems = ({
    attachments = ({
        registeredTypeIdentifiers = (
            "com.adobe.pdf",
            "public.file-url"
        );
    });
})}
複製代碼

爲了指定你的應用擴展能夠處理PDF文件,你能夠像這樣建立斷言字符串:

SUBQUERY (
    extensionItems,
    $extensionItem,
    SUBQUERY (
        $extensionItem.attachments,
        $attachment,
        ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.adobe.pdf"
    ).@count == $extensionItem.attachments.@count
).@count == 1
複製代碼

如下是更復雜的斷言語句的示例:

SUBQUERY (
    extensionItems,
    $extensionItem,
    SUBQUERY (
        $extensionItem.attachments,
        $attachment,
        ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "org.appextension.action-one" ||
        ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "org.appextension.action-two"
    ).@count == $extensionItem.attachments.@count
).@count == 1

複製代碼

此語句遍歷一個NSExtensionItem對象數組,其次是遍歷attachments每一個擴展項中的數組。對於每一個附件,謂詞評估附件中每一個表示的統一類型標識符(UTI)。當附件表示UTI符合兩個不一樣的指定UTI中的任何一個(您在每一個UTI-CONFORMS-TO操做員的右側看到)時,收集該UTI以進行最終比較測試。TRUE若是應用程序擴展名僅提供了一個支持UTI的擴展項附件,則返回最後一行。

開發過程當中,在你建立斷言語句以前你可使用TRUEPREDICATE常量(結果爲true)測試你的代碼路徑。更多斷言語句的語法知識請參閱Predicate Format String Syntax。

重要提示:在將你的載體應用上傳App Store以前,要確保全部的 TRUEPREDICATE 常量已經替換爲指定的斷言語句或 NSExtensionActivationRule 關鍵字,否則載體應用會被App Store拒絕。

配置載體應用以適用於老版本的iOS系統

若是你在主體應用中使用了內嵌框架,那麼它就能夠在iOS8.0以後的版本中使用,即使內嵌框架不支持老版本的系統也不要緊。

使主體應用能作到上述這一點的是 dlopen 命令,它可使你使用條件連接和加載框架包的機制。你可使用這個命令來代替編譯時連接,你能夠在 Xcode 的 General 選項或 Build Phases 選項中對該命令進行編輯。其原理就是隻有當主體應用在 iOS8.0 或更高的版本中運行時,纔會連接使用內嵌框架。

您必須在有條件地 framework bundle的代碼語句中使用Objective-C而不是Swift。您的應用程序的其他部分能夠用任何一種語言編寫,內嵌框架自己也能夠用任何一種語言編寫。

調用以後dlopen,使用如下類型的語句訪問內嵌框架類:

MyLoadedClass *loadedClass = [[NSClassFromString (@"MyClass") alloc] init]; 
複製代碼

重要提示:若是你的主體應用使用了內嵌框架,那麼就必需要支持arm64架構,不然會被App Store拒絕。

設置Xcode項目中應用擴展的條件連接

1.將每個應用擴展的運行系統版本設置爲iOS8.0或更高,一般選中Xcode中的target,在General選項中設置Deployment info。 2.將你主體應用的運行系統版本設置爲你想支持的最低iOS版本。 3.在你的主體應用中,經過 systemVersion 方法,在運行時檢查判斷iOS的版本,並判斷是否執行dlopen命令。只有你的載體應用在iOS8.0或更高的版本中運行時纔會指定dlopen命令。進行此調用時,請務必使用Objective-C,而不是Swift。

特定的iOS API經過dlopen命令使用內嵌框架。你必須選擇性的使用這些API,就像使用 dlopen 命令時那樣。這些API都是 CFBundleRef 的封裝類型:

CFBundleGetFunctionPointerForName

CFBundleGetFunctionPointersforNames

還有來自NSBundle類的方法:

loadloadAndReturnError: classNamed:

由於你通常會將載體應用的運行系統版本配置爲較低的版本,因此這些API一般都是在運行時檢查,只有確保載體應用在iOS8.0或更高版本中運行時纔會使用這些API。

相關文章
相關標籤/搜索