[轉(zhuǎn)]extension是iOS8新開放的一種對(duì)幾個(gè)固定系統(tǒng)區(qū)域的擴(kuò)展機(jī)制训裆,它可以在一定程度上彌補(bǔ)iOS的沙盒機(jī)制對(duì)應(yīng)用間通信的限制。

一蜀铲、關(guān)于App Extensions

extension是iOS8新開放的一種對(duì)幾個(gè)固定系統(tǒng)區(qū)域的擴(kuò)展機(jī)制边琉,它可以在一定程度上彌補(bǔ)iOS的沙盒機(jī)制對(duì)應(yīng)用間通信的限制。

extension的出現(xiàn)记劝,為用戶提供了在其它應(yīng)用中使用我們應(yīng)用提供的服務(wù)的便捷方式变姨,比如用戶可以在Today的widgets中查看應(yīng)用展示的簡(jiǎn)略信息,而不用再進(jìn)到我們的應(yīng)用中厌丑,這將是一種全新的用戶體驗(yàn)定欧;但是,extension的出現(xiàn)可能會(huì)減少用戶啟動(dòng)應(yīng)用的次數(shù)怒竿,同時(shí)還會(huì)增大開發(fā)者的工作量砍鸠。

幾個(gè)關(guān)鍵詞

extension point

系統(tǒng)中支持extension的區(qū)域,extension的類別也是據(jù)此區(qū)分的愧口,iOS上共有Today睦番、Share、Action耍属、Photo Editing托嚣、Storage Provider、Custom keyboard幾種厚骗,其中Today中的extension又被稱為widget示启。

每種extension point的使用方式和適合干的活都不一樣,因此不存在通用的extension领舰。

app extension

即為本文所說的extension夫嗓。extension并不是一個(gè)獨(dú)立的app迟螺,它有一個(gè)包含在app bundle中的獨(dú)立bundle,extension的bundle后綴名是.appex舍咖。其生命周期也和普通app不同矩父,這些后文將會(huì)詳述。

extension不能單獨(dú)存在排霉,必須有一個(gè)包含它的containing app窍株。

另外,extension需要用戶手動(dòng)激活攻柠,不同的extension激活方式也不同球订,比如: 比如Today中的widget需要在Today中激活和關(guān)閉;Custom keyboard需要在設(shè)置中進(jìn)行相關(guān)設(shè)置瑰钮;Photo Editing需要在使用照片時(shí)在照片管理器中激活或關(guān)閉冒滩;Storage Provider可以在選擇文件時(shí)出現(xiàn);Share和Action可以在任何應(yīng)用里被激活浪谴,但前提是開發(fā)者需要設(shè)置Activation Rules开睡,以確定extension需要在合適出現(xiàn)。

containing app

盡管蘋果開放了extension较店,但是在iOS中extension并不能單獨(dú)存在士八,要想提交到AppStore容燕,必須將extension包含在一個(gè)app中提交梁呈,并且app的實(shí)現(xiàn)部分不能為空,這個(gè)包含extension的app就叫containing app。

extension會(huì)隨著containing app的安裝而安裝蘸秘,同時(shí)隨著containing app的卸載而卸載官卡。

host app

能夠調(diào)起extension的app被稱為host app,比如widget的host app就是Today醋虏。

二寻咒、extension和containing app、host app

2.1 extension和host app

extension和host app之間可以通過extensionContext屬性直接通信颈嚼,該屬性是新增加的UIViewController類別:

@interface?UIViewController(NSExtensionAdditions)??

//?Returns?the?extension?context.?Also?acts?as?a?convenience?method?for?a?view?controller?to?check?if?it?participating?in?an?extension?request.?

@property?(nonatomic,readonly,retain)?NSExtensionContext?*extensionContext?NS_AVAILABLE_IOS(8_0); @end?

實(shí)際上extension和host app之間是通過IPC(interprocess communication)實(shí)現(xiàn)的毛秘,只是蘋果把調(diào)用接口高度抽象了,我們并不需要關(guān)注那么底層的東西阻课。


2.2 containing app和host app

他們之間沒有任何直接關(guān)系叫挟,也從來不需要通信。

2.3 extension和containing app

這二者之間的關(guān)系最復(fù)雜限煞,糾糾纏纏扯不清關(guān)系抹恳。

不能直接通信

首先,盡管extension的bundle是放在containing app的bundle中署驻,但是他們是兩個(gè)完全獨(dú)立的進(jìn)程奋献,之間不能直接通信健霹。不過extension可以通過openURL的方式啟動(dòng)containing app(當(dāng)然也能啟動(dòng)其它app),不過必須通過extensionContext借助host app來實(shí)現(xiàn):

//通過openURL的方式啟動(dòng)Containing?APP?

-?(void)openURLContainingAPP?

{?

[self.extensionContext?openURL:[NSURL?URLWithString:@"appextension://123"]?

?????????????????completionHandler:^(BOOL?success)?{?

NSLog(@"open?url?result:%d",success);?

?????????????????}];?

}?

extension中是無法直接使用openURL的瓶蚂。

可以共享Shared resources

extension和containing app可以共同讀寫一個(gè)被稱為Shared resources的存儲(chǔ)區(qū)域糖埋,這是通過App Groups實(shí)現(xiàn)的,后文將會(huì)詳述窃这。


三者間的關(guān)系可以通過官網(wǎng)給的兩張圖片形象地說明:


containing app能夠控制extension的出現(xiàn)和隱藏

通過以下代碼阶捆,containing app可以讓extension出現(xiàn)或隱藏(當(dāng)然extension也可以讓自己隱藏):

//讓隱藏的插件重新顯示?

-?(void)showTodayExtension?

{?

[[NCWidgetController?widgetController]?setHasContent:YES?forWidgetWithBundleIdentifier:@"com.wangzz.app.extension"];?

}?


//隱藏插件?

-?(void)hiddeTodayExtension?

{?

[[NCWidgetController?widgetController]?setHasContent:NO?forWidgetWithBundleIdentifier:@"com.wangzz.app.extension"];?

}?

三、App Groups

這是iOS8新開放的功能钦听,在OS X上早就可用了洒试。它主要用于同一group下的app共享同一份讀寫空間,以實(shí)現(xiàn)數(shù)據(jù)共享朴上。

extension和containing app共同讀寫一份數(shù)據(jù)是很合理的需求垒棋,比如系統(tǒng)的股市應(yīng)用,widget和app中都需要展示幾個(gè)公司的股票數(shù)據(jù)痪宰,這就可以通過App Groups實(shí)現(xiàn)叼架。

3.1 功能開啟

為了便于后續(xù)操作,請(qǐng)先確保你的開發(fā)者賬號(hào)在Xcode上處于登錄狀態(tài)衣撬。


在app中開啟

App Groups位于:

TARGETS-->AppExtensionDemo-->Capabilities-->App?Groups?

找到以后乖订,將App Groups右上角的開關(guān)打開,然后選擇添加groups具练,比如我的是group.wangzz乍构,當(dāng)然這是為了測(cè)試隨便起得名字,正規(guī)點(diǎn)得命名規(guī)則應(yīng)該是:group.com.company.app扛点。

添加成功以后如下圖所示:

在extension中開啟

我創(chuàng)建的是widget哥遮,target名稱為TodayExtension,對(duì)應(yīng)的App Groups位于:

TARGETS-->TodayExtension-->Capabilities-->App?Groups?

開啟方式和app中一樣陵究,需要注意的是必須保證這里地App Groups名稱和app中的相同眠饮,即為group.wangzz。

四铜邮、extension和containing app數(shù)據(jù)共享

App Groups給我們提供了同一group內(nèi)app可以共同讀寫的區(qū)域仪召,可以通過以下方式實(shí)現(xiàn)數(shù)據(jù)共享:

4.1 通過NSUserDefaults共享數(shù)據(jù)

存數(shù)據(jù)

通過以下方式向NSUserDefaults中保存數(shù)據(jù):

-?(void)saveTextByNSUserDefaults?

{?

NSUserDefaults?*shared?=?[[NSUserDefaults?alloc]?initWithSuiteName:@"group.wangzz"];?

[shared?setObject:_textField.text?forKey:@"wangzz"];?

????[shared?synchronize];?

}?

需要注意的是:

1.保存數(shù)據(jù)的時(shí)候必須指明group id;

2.而且要注意NSUserDefaults能夠處理的數(shù)據(jù)只能是可plist化的對(duì)象松蒜,詳情見Property List Programming Guide扔茅。

3.為了防止出現(xiàn)數(shù)據(jù)同步問題,不要忘記調(diào)用[shared synchronize];

讀數(shù)據(jù)

對(duì)應(yīng)的讀取數(shù)據(jù)方式:

-?(NSString?*)readDataFromNSUserDefaults?

{?

NSUserDefaults?*shared?=?[[NSUserDefaults?alloc]?initWithSuiteName:@"group.wangzz"];?

NSString?*value?=?[shared?valueForKey:@"wangzz"];?

return?value;?

}?

4.2 通過NSFileManager共享數(shù)據(jù)

NSFileManager在iOS7提供了containerURLForSecurityApplicationGroupIdentifier方法牍鞠,可以用來實(shí)現(xiàn)app group共享數(shù)據(jù)咖摹。

保存數(shù)據(jù)

-?(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;?

}?

讀數(shù)據(jù)

-?(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;?

}?

在這里我試著保存和讀取的是字符串?dāng)?shù)據(jù),但讀寫SQlite我相信也是沒問題的难述。

數(shù)據(jù)同步

兩個(gè)應(yīng)用共同讀取同一份數(shù)據(jù)萤晴,就會(huì)引發(fā)數(shù)據(jù)同步問題吐句。WWDC2014的視頻中建議使用NSFileCoordination實(shí)現(xiàn)普通文件的讀寫同步,而數(shù)據(jù)庫可以使用CoreData,Sqlite也支持同步店读。

五嗦枢、extension和containing app代碼共享

和數(shù)據(jù)共享類似,extension和containing app很自然地會(huì)有一些業(yè)務(wù)邏輯上可以共用的代碼屯断,這時(shí)可以通過iOS8中剛開放使用的framework實(shí)現(xiàn)文虏。蘋果在App Extension Programming Guide中是這樣描述的:


In iOS 8.0 and later, you can use an embedded framework to share code between your extension and its containing app. For example, if you develop image-processing code that you want both your Photo Editing extension and its containing app to share, you can put the code into a framework and embed it in both targets.


即將framework分別嵌入到extension和containing app的target中實(shí)現(xiàn)代碼共享。但這樣豈不是需要分別要將framework分別copy到extension和containing app的main bundle中殖演?


參考extension和containing app數(shù)據(jù)共享氧秘,我試想能不能將framework只保存一份放在App Groups區(qū)域?


5.1 copy framework到App Groups


在app首次啟動(dòng)的時(shí)候?qū)ramework放到App Groups區(qū)域:

-?(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;?

}?


5.2 使用framework:

-?(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;?

}?

經(jīng)過測(cè)試趴久,竟然能夠加載成功丸相。

需要說明的是,這里只是說那么用是可以成功加載framework彼棍,但還面臨不少問題灭忠,比如如果用戶在啟動(dòng)app之前去使用extension,這時(shí)framework還沒有copy過去座硕,怎么處理弛作;另外iOS的機(jī)制或者蘋果的審核是否允許這樣使用等。

在一切確定下來之前還是乖乖按文檔中的方式使用吧华匾。

六映琳、生命周期

extension和普通app的最大區(qū)別之一是生命周期。

開始

在用戶通過host app點(diǎn)擊extension時(shí)瘦真,系統(tǒng)就會(huì)實(shí)例化extension應(yīng)用刊头,這是生命周期的開始黍瞧。

執(zhí)行任務(wù)

在extension啟動(dòng)以后诸尽,開始執(zhí)行它的使命。

終止

在用戶取消任務(wù)印颤,或者任務(wù)執(zhí)行結(jié)束您机,或者開啟了一個(gè)長(zhǎng)時(shí)后臺(tái)任務(wù)時(shí),系統(tǒng)會(huì)將其殺掉年局。

由此可見际看,extension就是為了任務(wù)而生!


下圖來自官方文檔矢否,它將生命周期劃分的更詳細(xì):



通過打印日志發(fā)現(xiàn)仲闽,Today中的widget在將Today切換到全部或者未讀通知時(shí)都會(huì)被殺掉。


七僵朗、 調(diào)試


extension和普通app的調(diào)試方式差不多赖欣,開始調(diào)試前先選中extension對(duì)應(yīng)的target屑彻,點(diǎn)擊run,就會(huì)彈出下圖所示選擇框:



需要選擇一個(gè)host app顶吮,這里選擇Today社牲。


然后即可和普通app一樣調(diào)試了,不過我在實(shí)際使用過程中悴了,發(fā)現(xiàn)有各種奇怪的事情搏恤,比如NSLog無法在控制臺(tái)輸出,應(yīng)該是bug吧湃交。


八熟空、 iOS8應(yīng)用文件系統(tǒng)


發(fā)現(xiàn)iOS8的文件系統(tǒng)發(fā)生了變化,新的文件系統(tǒng)將可執(zhí)行文件(即原來的.app文件)從沙盒中移到了另外一個(gè)地方搞莺,這樣感覺更合理痛阻。


測(cè)試代碼

下述代碼用于打印App Groups路徑、應(yīng)用的可執(zhí)行文件路徑腮敌、對(duì)應(yīng)的Documents路徑:

-?(void)logAppPath?

{?

//app?group路徑?

NSURL?*containerURL?=?[[NSFileManager?defaultManager]?containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"];?

NSLog(@"app?group:\n%@",containerURL.path);?


//打印可執(zhí)行文件路徑?

NSLog(@"bundle:\n%@",[[NSBundle?mainBundle]?bundlePath]);?


//打印documents?

????NSArray?*paths?=?NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,?NSUserDomainMask,?YES);?

????NSString?*path?=?[paths?objectAtIndex:0];?

NSLog(@"documents:\n%@",path);?

}?


containing app執(zhí)行結(jié)果

2014-06-23?19:35:03.944?AppExtensionDemo[7471:365131]?app?group:?

/private/var/mobile/Containers/Shared/AppGroup/89CCBFB1-CA5E-4C7F-80CB-A3EB9E841816?

2014-06-23?19:35:03.946?AppExtensionDemo[7471:365131]?bundle:?

/private/var/mobile/Containers/Bundle/Application/1AC73797-A3BB-4BDE-A647-3D083DA6871A/AppExtensionDemo.app?

2014-06-23?19:35:03.948?AppExtensionDemo[7471:365131]?documents:?

/var/mobile/Containers/Data/Application/E5E6E516-0163-4754-9D10-A5F6C33A6261/Documents?


extension執(zhí)行結(jié)果

Jun?23?19:37:49?autonavis-iPad?com.foogry.AppExtensionDemo.TodayExtension[7638]?:?app?group:?

/private/var/mobile/Containers/Shared/AppGroup/89CCBFB1-CA5E-4C7F-80CB-A3EB9E841816?

Jun?23?19:37:49?autonavis-iPad?com.foogry.AppExtensionDemo.TodayExtension[7638]?:?bundle:?

/private/var/mobile/Containers/Bundle/Application/596717B7-7CB8-4F53-BCD4-380F34ABD30F/AppExtensionDemo.app/PlugIns/com.foogry.AppExtensionDemo.TodayExtension.appex?

Jun?23?19:37:49?autonavis-iPad?com.foogry.AppExtensionDemo.TodayExtension[7638]?:?documents:?

/var/mobile/Containers/Data/PluginKitPlugin/57581433-3DBD-4930-971F-78D30C150E8A/Documents?


由此可見阱当,不管是extension還是containing app,他們的可執(zhí)行文件和保存數(shù)據(jù)的目錄都是分開存放的糜工,即所有app的可執(zhí)行文件都放在一個(gè)大目錄下弊添,保存數(shù)據(jù)的目錄保存在另一個(gè)大目錄下,同樣捌木,AppGroup放在另一個(gè)大目錄下油坝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市刨裆,隨后出現(xiàn)的幾起案子澈圈,更是在濱河造成了極大的恐慌,老刑警劉巖帆啃,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞬女,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡努潘,警方通過查閱死者的電腦和手機(jī)诽偷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疯坤,“玉大人报慕,你說我怎么就攤上這事⊙沟。” “怎么了眠冈?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)菌瘫。 經(jīng)常有香客問我蜗顽,道長(zhǎng)玄柠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任诫舅,我火速辦了婚禮羽利,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘刊懈。我一直安慰自己这弧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布虚汛。 她就那樣靜靜地躺著匾浪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卷哩。 梳的紋絲不亂的頭發(fā)上蛋辈,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音将谊,去河邊找鬼冷溶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛尊浓,可吹牛的內(nèi)容都是我干的逞频。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼栋齿,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼苗胀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瓦堵,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤基协,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后菇用,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澜驮,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年刨疼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泉唁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揩慕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扮休,到底是詐尸還是另有隱情迎卤,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布玷坠,位于F島的核電站蜗搔,受9級(jí)特大地震影響劲藐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜樟凄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一聘芜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缝龄,春花似錦汰现、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至炼绘,卻和暖如春嗅战,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俺亮。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工驮捍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脚曾。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓厌漂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親斟珊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苇倡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容