iOS 利用 framework 進行動態(tài)更新(轉(zhuǎn))

前言

目前 iOS 上的動態(tài)更新方案主要有以下 4 種:

HTML 5

lua(wax)hotpatch

react native

framework

前面三種都是通過在應(yīng)用內(nèi)搭建一個運行環(huán)境來實現(xiàn)動態(tài)更新(HTML 5 是原生支持)育谬,在用戶體驗捧毛、與系統(tǒng)交互上有一定的限制箱锐,對開發(fā)者的要求也更高(至少得熟悉 lua 或者 js)距潘。

使用 framework 的方式來更新可以不依賴第三方庫屹堰,使用原生的 OC/Swift 來開發(fā),體驗更好坤邪,開發(fā)成本也更低挪捕。

由于 Apple 不希望開發(fā)者繞過 App Store 來更新 app,因此?只有對于不需要上架的應(yīng)用,才能以 framework 的方式實現(xiàn) app 的更新浮毯。

主要思路

將 app 中的某個模塊(比如一個 tab)的內(nèi)容獨立成一個 framework 的形式動態(tài)加載完疫,在 app 的 main bundle 中,當 app 啟動時從服務(wù)器上下載新版本的 framework 并加載即可達到動態(tài)更新的目的亲轨。代碼放在了這里趋惨。

實戰(zhàn)

創(chuàng)建一個普通工程 DynamicUpdateDemo,其包含一個 framework 子工程 Module惦蚊。也可以將 Module 創(chuàng)建為獨立的工程器虾,創(chuàng)建工程的過程不再贅述。

依賴

在主工程的 Build Phases > Target Dependencies 中添加 Module蹦锋,并且添加一個 New Copy Files Phase兆沙。

這樣,打包時會將生成的 Module.framework 添加到 main bundle 的根目錄下莉掂。

加載

主要的代碼如下:

- (UIViewController *)loadFrameworkNamed:(NSString *)bundleName {? ? NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);? ? NSString *documentDirectory = nil;if ([paths count] !=0) {? ? ? ? documentDirectory = [paths objectAtIndex:0];? ? }? ? ? ? NSFileManager *manager = [NSFileManager defaultManager];? ? NSString *bundlePath = [documentDirectory stringByAppendingPathComponent:[bundleName stringByAppendingString:@".framework"]];// Check if new bundle existsif (![manager fileExistsAtPath:bundlePath]) {? ? ? ? NSLog(@"No framework update");? ? ? ? bundlePath = [[NSBundle mainBundle]? ? ? ? ? ? ? ? ? ? ? pathForResource:bundleName ofType:@"framework"];// Check if default bundle existsif (![manager fileExistsAtPath:bundlePath]) {? ? ? ? ? ? UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Oooops" message:@"Framework not found"delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];? ? ? ? ? ? [alertView show];return nil;? ? ? ? }? ? }// Load bundle? ? NSError *error = nil;? ? NSBundle *frameworkBundle = [NSBundle bundleWithPath:bundlePath];if (frameworkBundle && [frameworkBundle loadAndReturnError:&error]) {? ? ? ? NSLog(@"Load framework successfully");? ? }else {? ? ? ? NSLog(@"Failed to load framework with err: %@",error);return nil;? ? }// Load class? ? Class PublicAPIClass = NSClassFromString(@"PublicAPI");if (!PublicAPIClass) {? ? ? ? NSLog(@"Unable to load class");return nil;? ? }? ? ? ? NSObject *publicAPIObject = [PublicAPIClassnew];return [publicAPIObject performSelector:@selector(mainViewController)];

}

代碼先嘗試在 Document 目錄下尋找更新后的 framework葛圃,如果沒有找到,再在 main bundle 中尋找默認的 framework憎妙。

其中的關(guān)鍵是利用 OC 的動態(tài)特性NSClassFromString和performSelector加載 framework 的類并且執(zhí)行其方法库正。

framework 和 host 工程資源共用

第三方庫

ClassXXXisimplementedinbothXXXandXXX.Oneofthetwowillbeused.Whichoneisundefined.

這是當 framework 工程和 host 工程鏈接了相同的第三方庫或者類造成的。

為了讓打出的 framework 中不包含 host 工程中已包含的三方庫(如 cocoapods 工程編譯出的 .a 文件)厘唾,可以這樣:

刪除Build Phases > Link Binary With Libraries中的內(nèi)容(如有)褥符。此時編譯會提示三方庫中包含的符號找不到。

在 framework 的Build Settings > Other Linker Flags添加-undefined dynamic_lookup抚垃。?必須保證 host 工程編譯出的二進制文件中包含這些符號喷楣。

類文件

嘗試過在 framework 中引用 host 工程中已有的文件,通過Build Settings > Header Search Paths中添加相應(yīng)的目錄鹤树,Xcode 在編譯的時候可以成功(因為添加了-undefined dynamic_lookup)铣焊,并且 Debug 版本是可以正常運行的,但是 Release 版本動態(tài)加載時會提示找不到符號:

Error Domain=NSCocoaErrorDomain Code=3588"The bundle “YourFramework” couldn’t be loaded." (dlopen(/var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework,265): Symbol not found: _OBJC_CLASS_$_BorderedViewReferencedfrom: /var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFrameworkExpectedin: flatnamespacein /var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework) UserInfo=0x174276900 {NSLocalizedFailureReason=The bundle couldn’t be loaded., NSLocalizedRecoverySuggestion=Try reinstalling the bundle., NSFilePath=/var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework, NSDebugDescription=dlopen(/var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework,265): Symbol not found: _OBJC_CLASS_$_BorderedViewReferencedfrom: /var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFrameworkExpectedin: flatnamespacein /var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework, NSBundlePath=/var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework, NSLocalizedDescription=The bundle “YourFramework” couldn’t be loaded.}

因為 Debug 版本暴露了所有自定義類的符號以便于調(diào)試罕伯,因此你的 framework 可以找到相應(yīng)的符號曲伊,而 Release 版本則不會。

目前能想到的方法只有將相同的文件拷貝一份到 framework 工程里追他,并且更改類名熊昌。

訪問 framework 中的圖片

在 storyboard/xib 中可以直接訪問圖片,代碼中訪問的方法如下:

UIImage*image = [UIImage imageNamed:@"YourFramework.framework/imageName"]

注意:使用代碼方式訪問的圖片不可以放在 xcassets 中湿酸,否則得到的將是 nil。并且文件名必須以 @2x/@3x 結(jié)尾灭美,大小寫敏感推溃。因為imageNamed:默認在 main bundle 中查找圖片。

常見錯誤

Architecture

dlopen(/path/to/framework,9): no suitable image found.? Did find:/path/to/framework: mach-o, but wrong architecture

這是說 framework 不支持當前機器的架構(gòu)届腐。通過

lipo -info /path/to/MyFramework.framework/MyFramework

可以查看 framework 支持的 CPU 架構(gòu)铁坎。

碰到這種錯誤蜂奸,一般是因為編譯 framework 的時候,scheme 選擇的是模擬器硬萍,應(yīng)該選擇iOS Device扩所。

此外,如果沒有選擇iOS Device朴乖,編譯完成后祖屏,Products 目錄下的 .framework 文件名會一直是紅色,只有在 Derived Data 目錄下才能找到編譯生成的 .framework 文件买羞。

簽名

系統(tǒng)在加載動態(tài)庫時袁勺,會檢查 framework 的簽名,簽名中必須包含 TeamIdentifier 并且 framework 和 host app 的 TeamIdentifier 必須一致畜普。

如果不一致期丰,否則會報下面的錯誤:

Error loading /path/to/framework: dlopen(/path/to/framework,265): no suitable image found. Did find:/path/to/framework: mmap() error1

此外,如果用來打包的證書是 iOS 8 發(fā)布之前生成的吃挑,則打出的包驗證的時候會沒有 TeamIdentifier 這一項钝荡。這時在加載 framework 的時候會報下面的錯誤:

[deny-mmap] mapped file has no team identifier andis not a platform binary:/private/var/mobile/Containers/Bundle/Application/5D8FB2F7-1083-4564-94B2-0CB7DC75C9D1/YourAppNameHere.app/Frameworks/YourFramework.framework/YourFramework

可以通過codesign命令來驗證。

codesign -dv /path/to/YourApp.app

如果證書太舊舶衬,輸出的結(jié)果如下:

Executable=/path/to/YourApp.app/YourAppIdentifier=com.company.yourappFormat=bundle with Mach-O thin (armv7)CodeDirectory v=20100 size=221748 flags=0x0(none) hashes=11079+5 location=embeddedSignature size=4321Signed Time=2015年10月21日 上午10:18:37Info.plist entries=42TeamIdentifier=notsetSealed Resources version=2 rules=12 files=2451Internal requirementscount=1size=188

注意其中的TeamIdentifier=not set埠通。

采用 swift 加載 libswiftCore.dylib 這個動態(tài)庫的時候也會遇到這個問題,對此Apple 官方的解釋是:

To correct this problem, you will need to sign your app using code signing certificates with the Subject Organizational Unit (OU) set to your Team ID. All Enterprise and standard iOS developer certificates that are created after iOS 8 was released have the new Team ID field in the proper place to allow Swift language apps to run.

If you are an in-house Enterprise developer you will need to be careful that you do not revoke a distribution certificate that was used to sign an app any one of your Enterprise employees is still using as any apps that were signed with that enterprise distribution certificate will stop working immediately.

只能通過重新生成證書來解決這個問題约炎。但是 revoke 舊的證書會使所有用戶已經(jīng)安裝的植阴,用該證書打包的 app 無法運行。

等等圾浅,我們就跪在這里了嗎掠手?!

現(xiàn)在企業(yè)證書的有效期是三年狸捕,當證書過期時喷鸽,其打包的應(yīng)用就不能運行,那企業(yè)應(yīng)用怎么來更替證書呢灸拍?

Apple 為每個賬號提供了兩個證書做祝,這兩個證書可以同時生效,這樣在正在使用的證書過期之前鸡岗,可以使用另外一個證書打包發(fā)布混槐,讓用戶升級到新版本鹰霍。

也就是說贾费,可以使用另外一個證書來打包應(yīng)用似踱,并且可以覆蓋安裝使用舊證書打包的應(yīng)用。詳情可以看Apple 文檔次询。

You are responsible for managing your team’s certificates and provisioning profiles. Apple Developer Enterprise Program certificates expire after three years and provisioning profiles expire after one year.

Before a distribution certificate expires, create an additional distribution certificate, described in Creating Additional Enterprise Distribution Certificates. You cannot renew an expired certificate. Instead, replace the expired certificate with the new certificate, described in Replacing Expired Certificates.

If a distribution provisioning profile expires, verify that you have a valid distribution certificate and renew the provisioning profile, described in Renewing Expired Provisioning Profiles.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末健芭,一起剝皮案震驚了整個濱河市摸柄,隨后出現(xiàn)的幾起案子骚秦,更是在濱河造成了極大的恐慌,老刑警劉巖脯厨,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铅祸,死亡現(xiàn)場離奇詭異,居然都是意外死亡合武,警方通過查閱死者的電腦和手機临梗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來眯杏,“玉大人夜焦,你說我怎么就攤上這事∑穹罚” “怎么了茫经?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長萎津。 經(jīng)常有香客問我卸伞,道長,這世上最難降的妖魔是什么锉屈? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任荤傲,我火速辦了婚禮,結(jié)果婚禮上颈渊,老公的妹妹穿的比我還像新娘遂黍。我一直安慰自己,他們只是感情好俊嗽,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布雾家。 她就那樣靜靜地躺著,像睡著了一般绍豁。 火紅的嫁衣襯著肌膚如雪芯咧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天竹揍,我揣著相機與錄音敬飒,去河邊找鬼。 笑死芬位,一個胖子當著我的面吹牛无拗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播昧碉,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼蓝纲,長吁一口氣:“原來是場噩夢啊……” “哼阴孟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起税迷,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锹漱,沒想到半個月后箭养,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡哥牍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年毕泌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗅辣。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡撼泛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出澡谭,到底是詐尸還是另有隱情愿题,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布蛙奖,位于F島的核電站潘酗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏雁仲。R本人自食惡果不足惜仔夺,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望攒砖。 院中可真熱鬧缸兔,春花似錦、人聲如沸吹艇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掐暮。三九已至蝎抽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間路克,已是汗流浹背樟结。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留精算,地道東北人瓢宦。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像灰羽,于是被迫代替她去往敵國和親驮履。 傳聞我的和親對象是個殘疾皇子鱼辙,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

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