一、App Extension的介紹
App Extension可以讓你擴展你APP的自定義功能和內容,使用戶可以在與其他應用或者系統進行互動的時候去使用它。app extension即為本文所說的extension。extension並不是一個獨立的app,它有一個包含在app bundle中的獨立bundle,extension的bundle后綴名是.appex。其生命周期也和普通app不同,這些后文將會詳述。extension不能單獨存在,必須有一個包含它的containing app。擴展(app Extension )是 iOS 8 中引入的一個非常重要的新特性
我們平時看到的Widget、微信和QQ的share等等,都是App Extension,下圖是一些例子:

二、 Extension的種類
我們可以在Xcode的File--->New--->Target里面看到不同平台的Extension,包括iOS、watchOS、tvOS、macOS等等。這里主要介紹iOS,主要包括以下幾種Extensions:
也可直接參考官方文檔
iOS 8 系統有 6 個支持擴展的系統區域,分別是 Today 、 Share 、 Action 、 Photo Editing 、 Storage Provider 、 Custom keyboard 。支持擴展的系統區域也被稱為擴展點。
Today Widget
Today擴展 可以快速獲取更新或者在通知中心的近日視圖中執行一項快速任務。對於賽事比分,股票、天氣、快遞這類需要實時獲取的信息,可以在通知中心的Today 視圖中創建一個 Today 擴展實現。 Today 擴展又稱為 Widget 。
Share Extension
分享擴展,發布一個共享網站或者與其他應用共享內容,在 iOS 8 之前,用戶只有 Facebook,Twitter 等有限的幾個分享選項可以選擇。在 iOS 8 中,開發者可以創建自定義的分享選項。
Action Extension
動作擴展,在另一個應用程序的上下文中操作或者查看內容, 在所有支持的擴展點中擴展性最強的一個。它可以實現轉換另一個 app 上下文中的內容。蘋果在 WWDC 大會上演示了一個 Bing 翻譯動作擴展,它可以將在 Safari 中選中的文本翻譯成不同的語言。
Photo Editing
圖片編輯擴展,在照片app中編輯照片或者視頻,在 iOS 8 之前,如果你想為你的照片添加一個特殊的濾鏡,你需要進入第三方 app 中,這個過程是相當繁瑣的。在 iOS 8 中,你可以直接在 Photos 中使用第三方 app ,如 Instagram , VSCO cam 、 Aviary 提供的 Photo Editing 擴展完成對圖片的編輯,而無需離開當前的 app 。
Document Provider
Document Provider 讓跨多個文件存儲服務之間的管理變得更簡單。類似 Dropbox 、 Google Drive 等存儲提供商通過在 iOS 8 中提供一個 Document Provider 擴展, app 直接可以使用這些擴展檢索和存儲文件而不再需要創建不必要的拷貝。
Custom Keyboard
鍵盤擴展,例如第三方的鍵盤,搜狗輸入法,百度輸入法等。蘋果公司在 2007 年率先推出了觸摸屏鍵盤,但一直沒多大改進。在這一方面, Android 則將鍵盤權限開放給了第三方開發者,所以出現了許多像 Swype , SwiftKey 等優秀的鍵盤輸入法。在 iOS 8 中,蘋果終於將鍵盤權限開發給了第三方開發者,自定義鍵盤輸入法可以讓用戶在整個系統范圍內使用。
以下是iOS 9和之后中新增擴展
1.Audio Unit Extension:音頻單元擴展
2.Broadcast UI Extension:廣播UI 擴展
3.Broadcast Upload Extension:廣播上傳擴展
4.Call Directory Extension:呼叫目錄擴展
5.Content Blocker Extension:內容攔截器擴展
6.iMessage Extension:消息的擴展
7.Intents Extension:Intents擴展
8.Intents UI Extension:Intents UI擴展
9.Notification Content Extension:通知內容擴展
10.Notification Service Extension:通知服務擴展
11.Shared Links Extension:分享鏈接擴展
12.Spotlight Index Extension:Spotlight 索引擴展
13.Sticker Pack Extension:貼紙包擴展
三、App Extensions的生命周期
以下是蘋果官方提供的圖片:

1.用戶選擇要使用的App extension
2.系統啟動App Extension
3.App Extension 代碼運行
4.運行完之后系統kill掉App Extension
這就是App Extension的生命周期,舉個例子:
一個Share Extension,在圖庫里面你選擇了一張圖片,然后點擊分享,選擇你的Share Extension(第一步),此時系統會啟動你的Share Extension(第二步)。然后你將選擇的圖片分享到指定的程序(例如微信的發送給朋友)(第三步)。接下來分享頁面關閉,系統kill掉了Share Extension。
四、App Extension的通信方式
App Extension主要的通信是和他的host app
Host app (如微信) ; App extension (safari里面分享點擊出來的微信extension);Containing app (safari)

這個展示的就是正在運行的App Extension、host app和containing app之間的關系。可以看出:Containing App和app Extension並沒有直接的溝通。甚至有的時候Containing app可以不運行,而App Extension直接運行。Containing app和Host app沒有任何的溝通。
在一個典型的request/response中,系統打開代表host app(圖庫)的extension(微信分享的share extension),把host app提供的數據(圖片和選擇的好友)輸送到extension的context,然后extension展示界面,提供一些功能任務(例如微信的分享到朋友)。
還有一種是app extension可以直接和他的containing app溝通:


例如Today Widget,可以直接告訴系統打開他的Containing app,只需要調用NSExtensionContext的openURL:CompletionHandler:方法即可。
app extension和containing app可以共同讀寫一個被稱為Shared resources的存儲區域,這是通過App Groups實現的。
這里需要注意的是:
但是如果你不怕蘋果審核不通過 或者 下架 也可以用以下方法打開:
// UIWebView *webView = [[UIWebView alloc]init]; // webView.hidden = YES; // NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:@"weixin://"]]; // [webView loadRequest:request]; // [self.view addSubview:webView]; //這個方法已經打不開了 UIResponder* responder = self; while ((responder = [responder nextResponder]) != nil) { if ([responder respondsToSelector:@selector(openURL:)] == YES) { [responder performSelector:@selector(openURL:) withObject:[NSURL URLWithString:[NSString stringWithFormat:@"weixin://"]]]; } }
stackoverflow 里面有關於 Share Extension to open containing app 相關的解答
五、App Groups 實現數據共享
- TARGETS-->AppExtensionDemo-->Capabilities-->App Groups
- TARGETS-->TodayExtension-->Capabilities-->App Groups
三、extension和containing app數據共享
- (void)saveTextByNSUserDefaults { NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"]; [shared setObject:_textField.text forKey:@"wangzz"]; [shared synchronize]; }
- (NSString *)readDataFromNSUserDefaults { NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"]; NSString *value = [shared valueForKey:@"wangzz"]; return value; }
- (BOOL)saveTextByNSFileManager { NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"]; NSString *value = _textField.text; BOOL result = [value writeToURL:containerURL atomically:YES encoding:NSUTF8StringEncoding error:&err]; if (!result) { NSLog(@"%@",err); } else { NSLog(@"save value:%@ success.",value); } return result; }
- (NSString *)readTextByNSFileManager { NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"]; NSString *value = [NSString stringWithContentsOfURL:containerURL encoding:NSUTF8StringEncoding error:&err]; return value; }
- (BOOL)copyFrameworkFromMainBundleToAppGroup { NSFileManager *manager = [NSFileManager defaultManager]; NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; NSString *sorPath = [NSString stringWithFormat:@"%@/Dylib.framework",[[NSBundle mainBundle] bundlePath]]; NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path]; BOOL removeResult = [manager removeItemAtPath:desPath error:&err]; if (!removeResult) { NSLog(@"%@",err); } else { NSLog(@"remove success."); } BOOL copyResult = [[NSFileManager defaultManager] copyItemAtPath:sorPath toPath:desPath error:&err]; if (!copyResult) { NSLog(@"%@",err); } else { NSLog(@"copy success."); } return copyResult; }
- (BOOL)loadFrameworkInAppGroup { NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path]; NSBundle *bundle = [NSBundle bundleWithPath:desPath]; BOOL result = [bundle loadAndReturnError:&err]; if (result) { Class root = NSClassFromString(@"Person"); if (root) { Person *person = [[root alloc] init]; if (person) { [person run]; } } } else { NSLog(@"%@",err); } return result; }
六、在App Extension中不可以做的事情
一個app extension不能有以下情況:
1.訪問sharedApplication對象。因此不能使用任何該對象的防范
2.使用任何標記NS_EXTENSION_UNAVAILABLE宏的API,或者類似的宏,或者不可用framework里面的API,例如HealthKit framework不能用於app extensions
3.iOS設備訪問相機或者麥克風(iMessage app可以訪問這些資源,只要在Info.plist里面進行配置使用描述即可)
4.運行一個長時間的后台任務(根據不同平台而異)
5.使用AirDrop接收數據
