靜態(tài)庫,動(dòng)態(tài)庫與 Framework

靜態(tài)庫與動(dòng)態(tài)庫的區(qū)別

首先來看什么是庫,庫(Library)說白了就是一段編譯好的二進(jìn)制代碼,加上頭文件就可以供別人使用。

什么時(shí)候我們會用到庫呢?一種情況是某些代碼需要給別人使用,但是我們不希望別人看到源碼,就需要以庫的形式進(jìn)行封裝,只暴露出頭文件。另外一種情況是,對于某些不會進(jìn)行大的改動(dòng)的代碼,我們想減少編譯的時(shí)間,就可以把它打包成庫,因?yàn)閹焓且呀?jīng)編譯好的二進(jìn)制了,編譯的時(shí)候只需要

Link 一下,不會浪費(fèi)編譯時(shí)間。

上面提到庫在使用的時(shí)候需要 Link,Link 的方式有兩種,靜態(tài)和動(dòng)態(tài),于是便產(chǎn)生了靜態(tài)庫和動(dòng)態(tài)庫。

靜態(tài)庫

靜態(tài)庫即靜態(tài)鏈接庫(Windows 下的 .lib,Linux 和 Mac 下的 .a)。之所以叫做靜態(tài),是因?yàn)殪o態(tài)庫在編譯的時(shí)候會被直接拷貝一份,復(fù)制到目標(biāo)程序里,這段代碼在目標(biāo)程序里就不會再改變了。

靜態(tài)庫的好處很明顯,編譯完成之后,庫文件實(shí)際上就沒有作用了。目標(biāo)程序沒有外部依賴,直接就可以運(yùn)行。當(dāng)然其缺點(diǎn)也很明顯,就是會使用目標(biāo)程序的體積增大。

動(dòng)態(tài)庫

動(dòng)態(tài)庫即動(dòng)態(tài)鏈接庫(Windows 下的 .dll,Linux 下的 .so,Mac 下的 .dylib/.tbd)。與靜態(tài)庫相反,動(dòng)態(tài)庫在編譯時(shí)并不會被拷貝到目標(biāo)程序中,目標(biāo)程序中只會存儲指向動(dòng)態(tài)庫的引用。等到程序運(yùn)行時(shí),動(dòng)態(tài)庫才會被真正加載進(jìn)來。

動(dòng)態(tài)庫的優(yōu)點(diǎn)是,不需要拷貝到目標(biāo)程序中,不會影響目標(biāo)程序的體積,而且同一份庫可以被多個(gè)程序使用(因?yàn)檫@個(gè)原因,動(dòng)態(tài)庫也被稱作共享庫)。同時(shí),編譯時(shí)才載入的特性,也可以讓我們隨時(shí)對庫進(jìn)行替換,而不需要重新編譯代碼。動(dòng)態(tài)庫帶來的問題主要是,動(dòng)態(tài)載入會帶來一部分性能損失,使用動(dòng)態(tài)庫也會使得程序依賴于外部環(huán)境。如果環(huán)境缺少動(dòng)態(tài)庫或者庫的版本不正確,就會導(dǎo)致程序無法運(yùn)行(Linux 下喜聞樂見的 lib not found 錯(cuò)誤)。

iOS Framework

除了上面提到的 .a 和 .dylib/.tbd 之外,Mac OS/iOS 平臺還可以使用 Framework。Framework 實(shí)際上是一種打包方式,將庫的二進(jìn)制文件,頭文件和有關(guān)的資源文件打包到一起,方便管理和分發(fā)。

在 iOS 8 之前,iOS 平臺不支持使用動(dòng)態(tài) Framework,開發(fā)者可以使用的 Framework 只有蘋果自家的 UIKit.Framework,F(xiàn)oundation.Framework 等。這種限制可能是出于安全的考慮(見這里的討論)。換一個(gè)角度講,因?yàn)?iOS 應(yīng)用都是運(yùn)行在沙盒當(dāng)中,不同的程序之間不能共享代碼,同時(shí)動(dòng)態(tài)下載代碼又是被蘋果明令禁止的,沒辦法發(fā)揮出動(dòng)態(tài)庫的優(yōu)勢,實(shí)際上動(dòng)態(tài)庫也就沒有存在的必要了。

由于上面提到的限制,開發(fā)者想要在 iOS 平臺共享代碼,唯一的選擇就是打包成靜態(tài)庫 .a 文件,同時(shí)附上頭文件(例如微信的SDK)。但是這樣的打包方式不夠方便,使用時(shí)也比較麻煩,大家還是希望共享代碼都能能像 Framework 一樣,直接扔到工程里就可以用。于是人們想出了各種奇技淫巧去讓 Xcode Build 出 iOS 可以使用的 Framework,具體做法參考這里這里,這種方法產(chǎn)生的 Framework 還有 “偽”(Fake) Framework 和 “真”(Real) Framework 的區(qū)別。

iOS 8/Xcode 6 推出之后,iOS 平臺添加了動(dòng)態(tài)庫的支持,同時(shí) Xcode 6 也原生自帶了 Framework 支持(動(dòng)態(tài)和靜態(tài)都可以),上面提到的的奇技淫巧也就沒有必要了(新的做法參考這里)。為什么 iOS 8 要添加動(dòng)態(tài)庫的支持?唯一的理由大概就是 Extension 的出現(xiàn)。Extension 和 App 是兩個(gè)分開的可執(zhí)行文件,同時(shí)需要共享代碼,這種情況下動(dòng)態(tài)庫的支持就是必不可少的了。但是這種動(dòng)態(tài) Framework 和系統(tǒng)的 UIKit.Framework 還是有很大區(qū)別。系統(tǒng)的 Framework 不需要拷貝到目標(biāo)程序中,我們自己做出來的 Framework 哪怕是動(dòng)態(tài)的,最后也還是要拷貝到 App 中(App 和 Extension 的 Bundle 是共享的),因此蘋果又把這種 Framework 稱為Embedded Framework。

Swift 支持

跟著 iOS8 / Xcode 6 同時(shí)發(fā)布的還有 Swift。如果要在項(xiàng)目中使用外部的代碼,可選的方式只有兩種,一種是把代碼拷貝到工程中,另一種是用動(dòng)態(tài) Framework。使用靜態(tài)庫是不支持的。

造成這個(gè)問題的原因主要是 Swift 的運(yùn)行庫沒有被包含在 iOS 系統(tǒng)中,而是會打包進(jìn) App 中(這也是造成 Swift App 體積大的原因),靜態(tài)庫會導(dǎo)致最終的目標(biāo)程序中包含重復(fù)的運(yùn)行庫(這是蘋果自家的解釋)。同時(shí)拷貝 Runtime 這種做法也會導(dǎo)致在純 ObjC 的項(xiàng)目中使用 Swift 庫出現(xiàn)問題。蘋果聲稱等到 Swift 的 Runtime 穩(wěn)定之后會被加入到系統(tǒng)當(dāng)中,到時(shí)候這個(gè)限制就會被去除了(參考這個(gè)問題的問題描述,也是來自蘋果自家文檔)。

CocoaPods 的做法

在純 ObjC 的項(xiàng)目中,CocoaPods 使用編譯靜態(tài)庫 .a 方法將代碼集成到項(xiàng)目中。在 Pods 項(xiàng)目中的每個(gè) target 都對應(yīng)這一個(gè) Pod 的靜態(tài)庫。不過在編譯過程中并不會真的產(chǎn)出 .a 文件。如果需要 .a 文件的話,可以參考這里,或者使用CocoasPods-Packager這個(gè)插件。

當(dāng)不想發(fā)布代碼的時(shí)候,也可以使用 Framework 發(fā)布 Pod,CocoaPods 提供了vendored_framework選項(xiàng)來使用第三方 Framework,具體的做法可以參考這里這里。

對于 Swift 項(xiàng)目,CocoaPods 提供了動(dòng)態(tài) Framework 的支持,通過use_frameworks!選項(xiàng)控制。

更多有關(guān)代碼分發(fā)的擴(kuò)展資料可以參考這篇博客:http://geeklu.com/2014/02/objc-lib/

參考資料

https://stackoverflow.com/questions/2649334/difference-between-static-and-shared-libraries

https://stackoverflow.com/questions/25080914/will-ios-8-support-dynamic-linking

https://stackoverflow.com/questions/6245761/difference-between-framework-and-static-library-in-xcode4-and-how-to-call-them

http://blog.cocoapods.org/CocoaPods-0.36/

轉(zhuǎn)載自:https://skyline75489.github.io/post/2015-8-14_ios_static_dynamic_framework_learning.html

使用靜態(tài)庫的好處

1,模塊化,分工合作

2,避免少量改動(dòng)經(jīng)常導(dǎo)致大量的重復(fù)編譯連接

3,也可以重用,注意不是共享使用

動(dòng)態(tài)庫使用有如下好處:

1使用動(dòng)態(tài)庫,可以將最終可執(zhí)行文件體積縮小

2使用動(dòng)態(tài)庫,多個(gè)應(yīng)用程序共享內(nèi)存中得同一份庫文件,節(jié)省資源

3使用動(dòng)態(tài)庫,可以不重新編譯連接可執(zhí)行程序的前提下,更新動(dòng)態(tài)庫文件達(dá)到更新應(yīng)用程序的目的。

從1可以得出,將整個(gè)應(yīng)用程序分模塊,團(tuán)隊(duì)合作,進(jìn)行分工,影響比較小。

等其他好處,

從2可以看出,其實(shí)動(dòng)態(tài)庫應(yīng)該叫共享庫,那么從這個(gè)意義上來說,蘋果禁止iOS開發(fā)中使用動(dòng)態(tài)庫就可以理解了:

因?yàn)樵诂F(xiàn)在的iPhone,iPodTouch,iPad上面程序都是單進(jìn)程的,也就是某一時(shí)刻只有一個(gè)進(jìn)程在運(yùn)行,那么你寫個(gè)共享庫,

----共享給誰?(你使用的時(shí)候只有你一個(gè)應(yīng)用程序存在,其他的應(yīng)該被掛起了,即便是可以同時(shí)多個(gè)進(jìn)程運(yùn)行,別人能使用你的共享庫里的東西嗎?你這個(gè)是給你自己的程序定制的。)

----目前蘋果的AppStore不支持模塊更新,無法更新某個(gè)單獨(dú)文件(除非自己寫一個(gè)更新機(jī)制:有自己的服務(wù)端放置最新動(dòng)態(tài)庫文件)

至于蘋果為啥禁止ios開發(fā)使用動(dòng)態(tài)庫我就猜到上面?zhèn)z原因

深入理解iPhone靜態(tài)庫

在實(shí)際的編程過程中,通常會把一些公用函數(shù)制成函數(shù)庫,供其它程序使用,一則提搞了代碼的復(fù)用;二則提搞了核心技術(shù)的保密程度。所以在實(shí)際的項(xiàng)目開發(fā)中,經(jīng)常會使用到函數(shù)庫,函數(shù)庫分為靜態(tài)庫和動(dòng)態(tài)庫兩種。和多數(shù)人所熟悉的動(dòng)態(tài)語言和靜態(tài)語言一樣,這里的所謂靜態(tài)和動(dòng)態(tài)是相對編譯期和運(yùn)行期的:靜態(tài)庫在程序編譯時(shí)會被鏈接到目標(biāo)代碼中,程序運(yùn)行時(shí)將不再需要改靜態(tài)庫;而動(dòng)態(tài)庫在程序編譯時(shí)并不會被鏈接到目標(biāo)代碼中,只是在程序運(yùn)行時(shí)才被載入,因?yàn)樵诔绦蜻\(yùn)行期間還需要?jiǎng)討B(tài)庫的存在。

iPhone官方只支持靜態(tài)庫聯(lián)編。

深入理解framework(框架,其實(shí)相當(dāng)于靜態(tài)框架,不是動(dòng)態(tài)庫)

打包framework還是一個(gè)比較重要的功能,可以用來做一下事情:

(1)封裝功能模塊,比如有比較成熟的功能模塊封裝成一個(gè)包,然后以后自己或其他同事用起來比較方便。

(2)封裝項(xiàng)目,有時(shí)候會遇到這個(gè)情況,就是一家公司找了兩個(gè)開發(fā)公司做兩個(gè)項(xiàng)目,然后要求他們的項(xiàng)目中的一個(gè)嵌套進(jìn)另一個(gè)項(xiàng)目,此時(shí)也可以把唄嵌套的項(xiàng)目打包成framework放進(jìn)去,這樣比較方便。

我們?yōu)槭裁葱枰蚣埽‵ramework)?

要想用一種開發(fā)者友好的方式共享庫是很麻煩的。你不僅僅需要包含庫本身,還要加入所有的頭文件,資源等等。

蘋果解決這個(gè)問題的方式是框架(framework)?;旧?,這是含有固定結(jié)構(gòu)并包含了引用該庫時(shí)所必需的所有東西的文件夾。不幸的是,iOS禁止所有的動(dòng)態(tài)庫。同時(shí),蘋果也從Xcode中移除了創(chuàng)建靜態(tài)iOS框架的功能。

Xcode仍然可以支持創(chuàng)建框架的功能,重啟這個(gè)功能,我們需要對Xcode做一些小小的改動(dòng)。

把代碼封裝在靜態(tài)框架是被app store所允許的。盡管形式不同,本質(zhì)上它仍然是一種靜態(tài)庫。

框架(Framework)的類別

大部分框架都是動(dòng)態(tài)鏈接庫的形式。因?yàn)橹挥刑O果才能在iOS設(shè)備上安裝動(dòng)態(tài)庫,所以我們無法創(chuàng)建這種類型的框架。

靜態(tài)鏈接庫和動(dòng)態(tài)庫一樣,只不過它是在編譯時(shí)鏈接二進(jìn)制代碼,因此使用靜態(tài)庫不會有動(dòng)態(tài)庫那樣的問題(即除了蘋果誰也不能在iOS上使用動(dòng)態(tài)庫)。

“偽”框架是通過破解Xcode的目標(biāo)Bundle(使用某些腳本)來實(shí)現(xiàn)的。它在表面上以及使用時(shí)跟靜態(tài)框架并無區(qū)別?!皞巍笨蚣茼?xiàng)目的功能幾乎和真實(shí)的框架項(xiàng)目沒有區(qū)別(不是全部)。

“嵌入”框架是靜態(tài)框架的一個(gè)包裝,以便Xcode能獲取框架內(nèi)的資源(圖片、plist、nib等)。

本次發(fā)布包括了創(chuàng)建靜態(tài)框架和“偽”框架的模板,以及二者的“嵌入”框架。

用哪一種模板?

本次發(fā)布有兩個(gè)模板,每個(gè)模板都有“強(qiáng)”“弱”兩個(gè)類別。你可以選擇最適合一種(或者兩種都安裝上)。

最大的不同是Xcode不能創(chuàng)建“真”框架,除非你安裝靜態(tài)框架文件xcspec在Xcode中。這真是一個(gè)遺憾(這個(gè)文件是給項(xiàng)目使用的,而不是框架要用的)。

簡單第

簡單說,你可以這樣決定用哪一種模板:

如果你不想修改Xcode,那么請使用“偽”框架版本

如果你只是想共享二進(jìn)制(不是項(xiàng)目),兩種都可以

如果你想把框架共享給不想修改Xcode的開發(fā)者,使用“偽”框架版本

如果你想把框架共享給修改過Xcode的開發(fā)者,使用“真”框架版本

如果你想把框架項(xiàng)目作為另一個(gè)項(xiàng)目的依賴(通過workspace或者子項(xiàng)目的方式),請使用“真”框架(或者“偽”框架,使用-framework——見后)

如果你想在你的框架項(xiàng)目中加入其他靜態(tài)庫/框架,并把它們也鏈接到最終結(jié)果以便不需要單獨(dú)添加到用戶項(xiàng)目中,使用“偽”框架

“偽”框架

“偽”框架是破解的“reloacatable object file”(可重定位格式的目標(biāo)文件,

保存著代碼和數(shù)據(jù),適合于和其他的目標(biāo)文件連接到一起,用來創(chuàng)建一個(gè)可執(zhí)行目標(biāo)文件或者是一個(gè)可共享目標(biāo)文件),它可以讓Xcode編譯出類似框架的東西——其實(shí)也是一個(gè)bundle。

“偽框架”模板把整個(gè)過程分為幾個(gè)步驟,用某些腳本去產(chǎn)生一個(gè)真正的靜態(tài)框架(基于靜態(tài)庫而不是reloacatable object file)。而且,框架項(xiàng)目還是把它定義為wrapper.cfbundle類型,一種Xcode中的“二等公民”。

因此它跟“真”靜態(tài)框架一樣可以正常工作,但當(dāng)存在依賴關(guān)系時(shí)就有麻煩了。

依賴問題

如果不使用依賴,只是創(chuàng)建普通的項(xiàng)目是沒有任何問題的。但是如果使用了項(xiàng)目依賴(比如在workspace中),Xcode就悲劇了。當(dāng)你點(diǎn)擊“Link

Binary With

Libraries”下方的’+’按鈕時(shí),“偽框架”無法顯示在列表中。你可以從你的“偽”框架項(xiàng)目的Products下面將它手動(dòng)拖入,但當(dāng)你編輯你的主項(xiàng)目時(shí),會出現(xiàn)警告:

warning: skipping file '/somewhere/MyFramework.framework'

(unexpectedfile type 'wrapper.cfbundle' in Frameworks & Libraries

build phase)

并伴隨“偽”框架中的鏈接錯(cuò)誤。

幸運(yùn)的是,有個(gè)辦法來解決它。你可以在”O(jiān)ther Linker Flags”中用”-framwork”開關(guān)手動(dòng)告訴linker去使用你的框架進(jìn)行鏈接:

-framework MyFramework

警告仍然存在,但起碼能正確鏈接了。

添加其他的庫/框架

如果你加入其他靜態(tài)(不是動(dòng)態(tài))庫/框架到你的“偽”框架項(xiàng)目中,它們將“鏈接”進(jìn)你最終的二進(jìn)制框架文件中。在“真”框架項(xiàng)目中,它們是純引用,而不是鏈接。

你可以在項(xiàng)目中僅僅包含頭文件而不是靜態(tài)庫/框架本身的方式避免這種情況(以便編譯通過)。

“真”框架

“真”框架各個(gè)方面都符合“真”的標(biāo)準(zhǔn)。它是真正的靜態(tài)框架,正如使用蘋果在從Xcode中去除的那個(gè)功能所創(chuàng)建的一樣。

為了能創(chuàng)建真正的靜態(tài)框架項(xiàng)目,你必需在Xcode中安裝一個(gè)xcspec文件。

如果你發(fā)布一個(gè)“真”框架項(xiàng)目(而不是編譯),希望去編譯這個(gè)框架的人必需也安裝xcspec文件(使用本次發(fā)布的安裝腳本),以便Xcode能理解目標(biāo)類型。

注意:如果你正在發(fā)布完全編譯的框架,而不是框架項(xiàng)目,最終用戶并不需要安裝任何東西。

我已經(jīng)提交一個(gè)報(bào)告給蘋果,希望他們在Xcode中更新這個(gè)文件,但那需要一點(diǎn)時(shí)間.OpenRadarlink here

加其他靜態(tài)庫/框架

如果你加入其他靜態(tài)(不是動(dòng)態(tài))庫/框架到你的“真”框架項(xiàng)目,它們只會被引用,而不會象“偽”框架一樣被鏈接到最終的二進(jìn)制文件中。

從早期版本升級

如果你是從Mk6或者更早的版本升級,同時(shí)使用“真”靜態(tài)框架,并且使用Xcode4.2.1以前的版本,請運(yùn)行uninstall_legacy.sh以卸載早期用于Xcode的所有修正。然后再運(yùn)行install.sh,重啟Xcode。如果你使用Xcode4.3以后,只需要運(yùn)行install.sh并重啟Xcode。

安裝

分別運(yùn)行Real Framework目錄或Fake Framework目錄下的install.sh腳本進(jìn)行安裝(或者兩個(gè)你都運(yùn)行)。

重啟Xcode,你將在新項(xiàng)目向?qū)У腇ramework&Library下看到StaticiOS Framework(或者Fake Static iOS Framework)。

卸載請運(yùn)行unistall.sh腳本并重啟Xcode。

創(chuàng)建一個(gè)iOS框架項(xiàng)目

創(chuàng)建新項(xiàng)目。

項(xiàng)目類型選擇Framework&Library下的Static iOS Framework(或者Fake Static iOS Framework)。

選擇“包含單元測試”(可選的)。

在target中加入類、資源等。

凡是其他項(xiàng)目要使用的頭文件,必需聲明為public。進(jìn)入target的Build Phases頁,展開Copy Headers項(xiàng),把需要public的頭文件從Project或Private部分拖拽到Public部分。

編譯你的 iOS 框架

選擇指定target的scheme

修改scheme的Run配置(可選)。Run配置默認(rèn)使用Debug,但在準(zhǔn)備部署的時(shí)候你可能想使用Release。

編譯框架(無論目標(biāo)為iOS device和Simulator都會編譯出相同的二進(jìn)制,因此選誰都無所謂了)。

從Products下選中你的framework,“show in Finder”。

在build目錄下有兩個(gè)文件夾:(yourframework).frameworkand(your framework).embeddedframework.

如果你的框架只有代碼,沒有資源(比如圖片、腳本、xib、coredata的momd文件等),你可以把(yourframework).framework分發(fā)給你的用戶就行了。如果還包含有資源,你必需分發(fā)(your framework).embeddedframework給你的用戶。

為什么需要embedded framework?因?yàn)閄code不會查找靜態(tài)框架中的資源,如果你分發(fā)(your framework).framework, 則框架中的所有資源都不會顯示,也不可用。

一個(gè)embedded framework只是一個(gè)framework之外的附加的包,包括了這個(gè)框架的所有資源的符號鏈接。這樣做的目的是讓Xcode能夠找到這些資源。

使用iOS 框架

iOS框架和常規(guī)的Mac OS動(dòng)態(tài)框架差不多,只是它是靜態(tài)鏈接的而已。

在你的項(xiàng)目中使用一個(gè)框架,只需把它拖僅你的項(xiàng)目中。在包含頭文件時(shí),記住使用尖括號而不是雙引號括住框架名稱。例如,對于框架MyFramework:

#import

使用問題

Headers Not Found

如果Xcode找不到框架的頭文件,你可能是忘記將它們聲明為public了。參考“創(chuàng)建一個(gè)iOS框架項(xiàng)目”第5步。

No Such Product Type

如果你沒有安裝iOS Universal Framework在Xcode,并企圖編譯一個(gè)universal框架項(xiàng)目(對于“真”框架,不是“假”框架),這會導(dǎo)致下列錯(cuò)誤:

target specifies product type

'com.apple.product-type.framework.static',but there's no such product

type for the 'iphonesimulator' platform

為了編譯“真”iOS靜態(tài)框架,Xcode需要做一些改動(dòng),因此為了編譯“真”靜態(tài)框架項(xiàng)目,請?jiān)谒械拈_發(fā)環(huán)境中安裝它(對于使用框架的用戶不需要,只有要編譯框架才需要)。

The selected run destination is not valid for this action

有時(shí),Xcode出錯(cuò)并加載了錯(cuò)誤的active設(shè)置。首先,請嘗試重啟Xcode。如果錯(cuò)誤繼續(xù)存在,Xcode產(chǎn)生了一個(gè)壞的項(xiàng)目(因?yàn)閄code4的一個(gè)bug,任何類型的項(xiàng)目都會出現(xiàn)這個(gè)問題)。如果是這樣,你需要?jiǎng)?chuàng)建一個(gè)新項(xiàng)目重來一遍。

鏈接警告

第一次編譯框架target時(shí),Xcdoe會在鏈接階段報(bào)告找不到文件夾:

ld: warning: directory not found for

option'-L/Users/myself/Library/Developer/Xcode/DerivedData/MyFramework-ccahfoccjqiognaqraesrxdyqcne/Build/Products/Debug-iphoneos'

此時(shí),可以clean并重新編譯target,警告會消除。

Core Data momd not found

對于框架項(xiàng)目和應(yīng)用程序項(xiàng)目,Xcode會以不同的方式編譯momd(托管對象模型文件)。Xcode會簡單地在根目錄創(chuàng)建.mom文件,而不會創(chuàng)建一個(gè).momd目錄(目錄中包含VersionInfo.plist和.mom文件)。

這意味著,當(dāng)從一個(gè)embedded framework的model中實(shí)例化NSManagedObjectModel時(shí),你必需使用.mom擴(kuò)展名作為model的URL,而不是采用.momd擴(kuò)展名。

NSURL *modelURL = [[NSBundle mainBundle]URLForResource:@"MyModel" withExtension:@"mom"];

Unknown class MyClass in Interface Builder file.

由于靜態(tài)框架采用靜態(tài)鏈接,linker會剔除所有它認(rèn)為無用的代碼。不幸的是,linker不會檢查xib文件,因此如果類是在xib中引用,而沒有在O-C代碼中引用,linker將從最終的可執(zhí)行文件中刪除類。這是linker的問題,不是框架的問題(當(dāng)你編譯一個(gè)靜態(tài)庫時(shí)也會發(fā)生這個(gè)問題)。蘋果內(nèi)置框架不會發(fā)生這個(gè)問題,因?yàn)樗麄兪沁\(yùn)行時(shí)動(dòng)態(tài)加載的,存在于iOS設(shè)備固件中的動(dòng)態(tài)庫是不可能被刪除的。

有兩個(gè)解決的辦法:

讓框架的最終用戶關(guān)閉linker的優(yōu)化選項(xiàng),通過在他們的項(xiàng)目的Other Linker Flags中添加-ObjC和-all_load。

在框架的另一個(gè)類中加一個(gè)該類的代碼引用。例如,假設(shè)你有個(gè)MyTextField類,被linker剔除了。假設(shè)你還有一個(gè)MyViewController,它在xib中使用了MyTextField,MyViewController并沒有被剔除。你應(yīng)該這樣做:

在MyTextField中:

+ (void)forceLinkerLoad_ {}

在MyViewController中:

+(void) initialize {?????[MyTextField forceLinkerLoad_]; }

他們?nèi)匀恍枰砑?ObjC到linker設(shè)置,但不需要強(qiáng)制all_load了。

第2種方法需要你多做一點(diǎn)工作,但卻讓最終用戶避免在使用你的框架時(shí)關(guān)閉linker優(yōu)化(關(guān)閉linker優(yōu)化會導(dǎo)致object文件膨脹)。

unexpected file type 'wrapper.cfbundle' in Frameworks &Libraries build phase

這個(gè)問題發(fā)生在把“假”框架項(xiàng)目作為workspace的依賴,或者把它當(dāng)作子項(xiàng)目時(shí)(“真”框架項(xiàng)目沒有這個(gè)問題)。盡管這種框架項(xiàng)目產(chǎn)生了正確的靜態(tài)框架,但Xcode只能從項(xiàng)目文件中看出這是一個(gè)bundle,因此它在檢查依賴性時(shí)發(fā)出一個(gè)警告,并在linker階段跳過它。

你可以手動(dòng)添加一個(gè)命令讓linker在鏈接階段能正確鏈接。在依賴你的靜態(tài)框架的項(xiàng)目的OtherLinker Flags中加入:

-framework MyFramework

警告仍然存在, 但不會導(dǎo)致鏈接失敗。

Libraries being linked or not being linked into the finalframework

很不幸, “真”框架和“假”框架模板在處理引入的靜態(tài)庫/框架的工作方式不同的。

“真”框架模板采用正常的靜態(tài)庫生成步驟,不會鏈接其他靜態(tài)庫/框架到最終生產(chǎn)物中。

“假”框架模板采用“欺騙”Xcode的手段,讓它認(rèn)為是在編譯一個(gè)可重定位格式的目標(biāo)文件,在鏈接階段就如同編譯一個(gè)可執(zhí)行文件,把所有的靜態(tài)代碼文件鏈接到最終生成物中(盡管不會檢查是否確實(shí)目標(biāo)代碼)。為了實(shí)現(xiàn)象“真”框架一樣的效果,你可以只包含庫/框架的頭文件到你的項(xiàng)目中,而不需要包含庫/框架本身。

Unrecognized selector in (some class with a category method)

如果你的靜態(tài)庫或靜態(tài)框架包含了一個(gè)模塊(只在類別代碼中聲明,沒有類實(shí)現(xiàn)),linker會搞不清楚,并把代碼從二進(jìn)制文件中剔除。因?yàn)樵谧罱K生成的文件中沒有這個(gè)方法,所以當(dāng)調(diào)用這個(gè)類別中定義的方法時(shí),會報(bào)一個(gè)“unrecognizedselector”異常。

要解決這個(gè),在包含這個(gè)類別的模塊代碼中加一個(gè)“假的”類。linker發(fā)現(xiàn)存在完整的O-C類,會將類別代碼鏈接到模塊。

我寫了一個(gè)頭文件?LoadableCategory.h,以減輕這個(gè)工作量:

#import "SomeConcreteClass+MyAdditions.h"

#import

"LoadableCategory.h"??MAKE_CATEGORIES_LOADABLE(SomeConcreteClass_MyAdditions);

@implementation SomeConcreteClass(MyAdditions)

...

@end

在使用這個(gè)框架時(shí),仍然還需要在Build Setting的Other Linker Flags中加入-ObjC。

執(zhí)行任何代碼前單元測試崩潰

如果你在Xcode4.3中創(chuàng)建靜態(tài)框架(或庫)target時(shí),勾選了“withunit tests”,當(dāng)你試圖運(yùn)行單元測試時(shí),它會崩潰:

Thread 1: EXC_BAD_ACCESS (code=2, address=0x0) 0 0x00000000 --- 15 dyldbootstrap:start(...)

這是lldb中的一個(gè)bug。你可以用GDB來運(yùn)行單元測試。編輯scheme,選擇Test,在Info標(biāo)簽中將調(diào)試器Debugger從LLDB改為GDB。

原文鏈接:http://www.itdecent.cn/p/4666ce7dc622

前言

1.靜態(tài)庫和動(dòng)態(tài)庫有什么異同?

靜態(tài)庫:鏈接時(shí)完整地拷貝至可執(zhí)行文件中,被多次使用就有多份冗余拷貝。利用靜態(tài)函數(shù)庫編譯成的文件比較大,因?yàn)檎麄€(gè)

函數(shù)庫的所有數(shù)據(jù)都會被整合進(jìn)目標(biāo)代碼中,他的優(yōu)點(diǎn)就顯而易見了,即編譯后的執(zhí)行程序不需要外部的函數(shù)庫支持,因?yàn)樗惺褂玫暮瘮?shù)都已經(jīng)被編譯進(jìn)去了。當(dāng)然這也會成為他的缺點(diǎn),因?yàn)槿绻o態(tài)函數(shù)庫改變了,那么你的程序必須重新編譯。

動(dòng)態(tài)庫:鏈接時(shí)不復(fù)制,程序運(yùn)行時(shí)由系統(tǒng)動(dòng)態(tài)加載到內(nèi)存,供程序調(diào)用,系統(tǒng)只加載一次,多個(gè)程序共用,節(jié)省內(nèi)存。由于函數(shù)庫沒有被整合進(jìn)你的程序,而是程序運(yùn)行時(shí)動(dòng)態(tài)的申請并調(diào)用,所以程序的運(yùn)行環(huán)境中必須提供相應(yīng)的庫。動(dòng)態(tài)函數(shù)庫的改變并不影響你的程序,所以動(dòng)態(tài)函數(shù)庫的升級比較方便。

靜態(tài)庫和動(dòng)態(tài)庫都是閉源庫,只能拿來滿足某個(gè)功能的使用,不會暴露內(nèi)部具體的代碼信息,而從github上下載的第三方庫大多是開源庫

靜態(tài)庫和動(dòng)態(tài)庫都是由*.o目標(biāo)文件生成

使用靜態(tài)庫的好處

模塊化,分工合作

避免少量改動(dòng)經(jīng)常導(dǎo)致大量的重復(fù)編譯連接

也可以重用,注意不是共享使用

動(dòng)態(tài)庫使用有如下好處:

可以將最終可執(zhí)行文件體積縮小

多個(gè)應(yīng)用程序共享內(nèi)存中得同一份庫文件,節(jié)省資源

可以不重新編譯連接可執(zhí)行程序的前提下,更新動(dòng)態(tài)庫文件達(dá)到更新應(yīng)用程序的目的。

將整個(gè)應(yīng)用程序分模塊,團(tuán)隊(duì)合作,進(jìn)行分工,影響比較小。

其實(shí)動(dòng)態(tài)庫應(yīng)該叫共享庫,那么從這個(gè)意義上來說,蘋果禁止iOS開發(fā)中使用動(dòng)態(tài)庫就可以理解了: 因?yàn)樵诂F(xiàn)在的iPhone,iPodTouch,iPad上面程序都是單進(jìn)程的,也就是某一時(shí)刻只有一個(gè)進(jìn)程在運(yùn)行,那么你寫個(gè)共享庫

----共享給誰?(你使用的時(shí)候只有你一個(gè)應(yīng)用程序存在,其他的應(yīng)該被掛起了,即便是可以同時(shí)多個(gè)進(jìn)程運(yùn)行,別人能使用你的共享庫里的東西嗎?你這個(gè)是給你自己的程序定制的。)

----目前蘋果的AppStore不支持模塊更新,無法更新某個(gè)單獨(dú)文件(除非自己寫一個(gè)更新機(jī)制:有自己的服務(wù)端放置最新動(dòng)態(tài)庫文件)

至于蘋果為啥禁止ios開發(fā)使用動(dòng)態(tài)庫我就猜到上面?zhèn)z原因

2.這兩種庫都有哪些文件格式?

靜態(tài)庫:.a和.framework (windows:.lib , linux: .a)

動(dòng)態(tài)庫:.dylib和.framework(系統(tǒng)提供給我們的framework都是動(dòng)態(tài)庫!)(windows:.dll , linux: .so)

注意:兩者都有framework的格式,但是當(dāng)你創(chuàng)建一個(gè)framework文件時(shí),系統(tǒng)默認(rèn)是動(dòng)態(tài)庫的格式,如果想做成靜態(tài)庫,需要在buildSetting中將Mach-O Type選項(xiàng)設(shè)置為Static Library就行了!

3..a文件和.framework文件的區(qū)別?

.a是一個(gè)純二進(jìn)制文件,不能直接拿來使用,需要配合頭文件、資源文件一起使用。

將靜態(tài)庫打包的時(shí)候,只能打包代碼資源,圖片、本地json文件和xib等資源文件無法打包進(jìn)去,使用.a靜態(tài)庫的時(shí)候需要三個(gè)組成部分:.a文件+需要暴露的頭文件+資源文件;

.framework中除了有二進(jìn)制文件之外還有資源文件,可以拿來直接使用。

4.制作靜態(tài)庫需要注意的幾點(diǎn):

注意理解:無論是.a靜態(tài)庫還.framework靜態(tài)庫,我們需要的都是二進(jìn)制文件+.h+其它資源文件的形式,不同的是,.a本身就是二進(jìn)制文件,需要我們自己配上.h和其它文件才能使用,而.framework本身已經(jīng)包含了.h和其它文件,可以直接使用。

圖片資源的處理:兩種靜態(tài)庫,一般都是把圖片文件單獨(dú)的放在一個(gè).bundle文件中,一般.bundle的名字和.a或.framework的名字相同。.bundle文件很好弄,新建一個(gè)文件夾,把它改名為.bundle就可以了,右鍵,顯示包內(nèi)容可以向其中添加圖片資源。

category是我們實(shí)際開發(fā)項(xiàng)目中經(jīng)常用到的,把category打成靜態(tài)庫是沒有問題的,但是在用這個(gè)靜態(tài)庫的工程中,調(diào)用category中的方法時(shí)會有找不到該方法的運(yùn)行時(shí)錯(cuò)誤(selector

not recognized),解決辦法是:在使用靜態(tài)庫的工程中配置other linkerflags的值為-ObjC。

如果一個(gè)靜態(tài)庫很復(fù)雜,需要暴露的.h比較多的話,就可以在靜態(tài)庫的內(nèi)部創(chuàng)建一個(gè).h文件(一般這個(gè).h文件的名字和靜態(tài)庫的名字相同),然后把所有需要暴露出來的.h文件都集中放在這個(gè).h文件中,而那些原本需要暴露的.h都不需要再暴露了,只需要把.h暴露出來就可以了。

5.framework動(dòng)態(tài)庫的主要作用:

framework本來是蘋果專屬的內(nèi)部提供的動(dòng)態(tài)庫文件格式,但是自從2014年WWDC之后,開發(fā)者也可以自定義創(chuàng)建framework實(shí)現(xiàn)動(dòng)態(tài)更新(繞過apple

store審核,從服務(wù)器發(fā)布更新版本)的功能,這與蘋果限定的上架的app必須經(jīng)過apple

store的審核制度是沖突的,所以含有自定義的framework的app是無法在商店上架的,但是如果開發(fā)的是企業(yè)內(nèi)部應(yīng)用,就可以考慮嘗試使用動(dòng)態(tài)更新技術(shù)來將多個(gè)獨(dú)立的app或者功能模塊集成在一個(gè)app上面?。üP者開發(fā)的就是企業(yè)內(nèi)部使用的app,我們將企業(yè)官網(wǎng)中的板塊開發(fā)成4個(gè)獨(dú)立的app,然后將其改造為framework文件集成在一款平臺級的app當(dāng)中進(jìn)行使用)

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

HTML 5

lua(wax)hotpatch

react native

framework

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

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

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

主要思路

將 app 中的某個(gè)模塊(比如一個(gè) tab)的內(nèi)容獨(dú)立成一個(gè) framework 的形式動(dòng)態(tài)加載,在 app 的 main bundle 中,當(dāng) app 啟動(dòng)時(shí)從服務(wù)器上下載新版本的 framework 并加載即可達(dá)到動(dòng)態(tài)更新的目的。

實(shí)戰(zhàn)

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

依賴

在主工程的 Build Phases > Target Dependencies 中添加 Module,并且添加一個(gè) New Copy Files Phase。

這樣,打包時(shí)會將生成的 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 exists

if (![manager fileExistsAtPath:bundlePath]) {

NSLog(@"No framework update");

bundlePath = [[NSBundle mainBundle]

pathForResource:bundleName ofType:@"framework"];

// Check if default bundle exists

if (![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 = [PublicAPIClass new];

return [publicAPIObject performSelector:@selector(mainViewController)];

}

代碼先嘗試在 Document 目錄下尋找更新后的 framework,如果沒有找到,再在 main bundle 中尋找默認(rèn)的

framework。 其中的關(guān)鍵是利用 OC 的動(dòng)態(tài)特性 NSClassFromString 和 performSelector 加載

framework 的類并且執(zhí)行其方法。

framework 和 host 工程資源共用

第方三庫

Class XXX is implemented in both XXX and XXX. One of the two will be used. Which one is undefined.

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

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

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

在 framework 的 Build Settings > Other Linker Flags 添加 -undefined dynamic_lookup。必須保證 host 工程編譯出的二進(jìn)制文件中包含這些符號。

類文件

嘗試過在 framework 中引用 host 工程中已有的文件,通過 Build Settings > Header Search

Paths 中添加相應(yīng)的目錄,Xcode 在編譯的時(shí)候可以成功(因?yàn)樘砑恿?-undefined dynamic_lookup),并且

Debug 版本是可以正常運(yùn)行的,但是 Release 版本動(dòng)態(tài)加載時(shí)會提示找不到符號:

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_$_BorderedView

Referenced from: /var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework

Expected in: flat namespace

in /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_$_BorderedView

Referenced from: /var/mobile/Containers/Bundle/Application/5691FB75-408A-4D9A-9347-BC7B90D343C1/YourApp.app/YourFramework.framework/YourFramework

Expected in: flat namespace

in /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.}

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

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

訪問 framework 中的圖片

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

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

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

常見錯(cuò)誤

Architecture

dlopen(/path/to/framework, 9): no suitable image found.? Did find:

/path/to/framework: mach-o, but wrong architecture

這是說 framework 不支持當(dāng)前機(jī)器的架構(gòu)。 通過

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

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

碰到這種錯(cuò)誤,一般是因?yàn)榫幾g framework 的時(shí)候,scheme 選擇的是模擬器,應(yīng)該選擇iOS Device。

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

關(guān)于other linker flag

使用靜態(tài)庫或者動(dòng)態(tài)庫的時(shí)候極易發(fā)生鏈接錯(cuò)誤,而且大多發(fā)生在加載framework中category的情況!根本原因在于Objective-C的鏈接器并不會為每個(gè)方法建立符號表,而是僅僅為類建立了符號表。這樣的話,如果靜態(tài)庫中定義了已存在的一個(gè)類的分類,鏈接器就會以為這個(gè)類已經(jīng)存在,不會把分類和核心類的代碼合起來。這樣的話,在最后的可執(zhí)行文件中,就會缺少分類里的代碼,這樣函數(shù)調(diào)用就失敗了。常見的設(shè)置方法就是在other linker flag中添加一個(gè)語句:-all_load,但是這樣也并不是萬能的,具體解析請參考鏈接:http://my.oschina.net/u/728866/blog/194741

注意:當(dāng)flag里面添加了注釋卻還是無法使用的時(shí)候,可能報(bào)flag與bitcode沖突的問題尤其是第三方庫可能和bitcode沖突),這樣的話就需要將bitcode設(shè)置為NO!

bitcode的具體作用不做詳談,可參考:http://www.itdecent.cn/p/3e1b4e2d06c6

簽名

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

如果不一致,否則會報(bào)下面的錯(cuò)誤:

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

/path/to/framework: mmap() error 1

此外,如果用來打包的證書是 iOS 8 發(fā)布之前生成的,則打出的包驗(yàn)證的時(shí)候會沒有 TeamIdentifier 這一項(xiàng)。這時(shí)在加載 framework 的時(shí)候會報(bào)下面的錯(cuò)誤:

[deny-mmap] mapped file has no team identifier and is not a platform binary:

/private/var/mobile/Containers/Bundle/Application/5D8FB2F7-1083-4564-94B2-0CB7DC75C9D1/YourAppNameHere.app/Frameworks/YourFramework.framework/YourFramework

可以通過 codesign 命令來驗(yàn)證。

codesign -dv /path/to/YourApp.app

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

Executable=/path/to/YourApp.app/YourApp

Identifier=com.company.yourapp

Format=bundle with Mach-O thin (armv7)

CodeDirectory v=20100 size=221748 flags=0x0(none) hashes=11079+5 location=embedded

Signature size=4321

Signed Time=2015年10月21日 上午10:18:37

Info.plist entries=42

TeamIdentifier=not set

Sealed Resources version=2 rules=12 files=2451

Internal requirements count=1 size=188

注意其中的 TeamIdentifier=not set。

采用 swift 加載 libswiftCore.dylib 這個(gè)動(dòng)態(tài)庫的時(shí)候也會遇到這個(gè)問題,對此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.

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

等等,我們就跪在這里了嗎?!

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

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

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

深入理解iPhone靜態(tài)庫

在實(shí)際的編程過程中,通常會把一些公用函數(shù)制成函數(shù)庫,供其它程序使用,一則提搞了代碼的復(fù)用;二則提搞了核心技術(shù)的保密程度。所以在實(shí)際的項(xiàng)目開發(fā)中,經(jīng)常會使用到函數(shù)庫,函數(shù)庫分為靜態(tài)庫和動(dòng)態(tài)庫兩種。和多數(shù)人所熟悉的動(dòng)態(tài)語言和靜態(tài)語言一樣,這里的所謂靜態(tài)和動(dòng)態(tài)是相對編譯期和運(yùn)行期的:靜態(tài)庫在程序編譯時(shí)會被鏈接到目標(biāo)代碼中,程序運(yùn)行時(shí)將不再需要改靜態(tài)庫;而動(dòng)態(tài)庫在程序編譯時(shí)并不會被鏈接到目標(biāo)代碼中,只是在程序運(yùn)行時(shí)才被載入,因?yàn)樵诔绦蜻\(yùn)行期間還需要?jiǎng)討B(tài)庫的存在。

深入理解framework(框架,相當(dāng)于靜態(tài)框架,不是動(dòng)態(tài)庫)

打包framework還是一個(gè)比較重要的功能,可以用來做一下事情:

封裝功能模塊,比如有比較成熟的功能模塊封裝成一個(gè)包,然后以后自己或其他同事用起來比較方便。

封裝項(xiàng)目,有時(shí)候會遇到這個(gè)情況,就是一家公司找了兩個(gè)開發(fā)公司做兩個(gè)項(xiàng)目,然后要求他們的項(xiàng)目中的一個(gè)嵌套進(jìn)另一個(gè)項(xiàng)目,此時(shí)也可以把唄嵌套的項(xiàng)目打包成framework放進(jìn)去,這樣比較方便。

我們?yōu)槭裁葱枰蚣埽‵ramework)?

要想用一種開發(fā)者友好的方式共享庫是很麻煩的。你不僅僅需要包含庫本身,還要加入所有的頭文件,資源等等。

蘋果解決這個(gè)問題的方式是框架(framework)?;旧希@是含有固定結(jié)構(gòu)并包含了引用該庫時(shí)所必需的所有東西的文件夾。不幸的是,iOS禁止所有的動(dòng)態(tài)庫。同時(shí),蘋果也從Xcode中移除了創(chuàng)建靜態(tài)iOS框架的功能。

Xcode仍然可以支持創(chuàng)建框架的功能,重啟這個(gè)功能,我們需要對Xcode做一些小小的改動(dòng)。

把代碼封裝在靜態(tài)框架是被app store所允許的。盡管形式不同,本質(zhì)上它仍然是一種靜態(tài)庫。

框架(Framework)的類別

大部分框架都是動(dòng)態(tài)鏈接庫的形式。因?yàn)橹挥刑O果才能在iOS設(shè)備上安裝動(dòng)態(tài)庫,所以我們無法創(chuàng)建這種類型的框架。

靜態(tài)鏈接庫和動(dòng)態(tài)庫一樣,只不過它是在編譯時(shí)鏈接二進(jìn)制代碼,因此使用靜態(tài)庫不會有動(dòng)態(tài)庫那樣的問題(即除了蘋果誰也不能在iOS上使用動(dòng)態(tài)庫)。

“偽”框架是通過破解Xcode的目標(biāo)Bundle(使用某些腳本)來實(shí)現(xiàn)的。它在表面上以及使用時(shí)跟靜態(tài)框架并無區(qū)別?!皞巍笨蚣茼?xiàng)目的功能幾乎和真實(shí)的框架項(xiàng)目沒有區(qū)別(不是全部)。

“嵌入”框架是靜態(tài)框架的一個(gè)包裝,以便Xcode能獲取框架內(nèi)的資源(圖片、plist、nib等)。 本次發(fā)布包括了創(chuàng)建靜態(tài)框架和“偽”框架的模板,以及二者的“嵌入”框架。

用哪一種模板?

本次發(fā)布有兩個(gè)模板,每個(gè)模板都有“強(qiáng)”“弱”兩個(gè)類別。你可以選擇最適合一種(或者兩種都安裝上)。 最大的不同是Xcode不能創(chuàng)建“真”框架,除非你安裝靜態(tài)框架文件xcspec在Xcode中。這真是一個(gè)遺憾(這個(gè)文件是給項(xiàng)目使用的,而不是框架要用的)。

簡單說,你可以這樣決定用哪一種模板:

如果你不想修改Xcode,那么請使用“偽”框架版本

如果你只是想共享二進(jìn)制(不是項(xiàng)目),兩種都可以

如果你想把框架共享給不想修改Xcode的開發(fā)者,使用“偽”框架版本

如果你想把框架共享給修改過Xcode的開發(fā)者,使用“真”框架版本

如果你想把框架項(xiàng)目作為另一個(gè)項(xiàng)目的依賴(通過workspace或者子項(xiàng)目的方式),請使用“真”框架(或者“偽”框架,使用-framework——見后)

如果你想在你的框架項(xiàng)目中加入其他靜態(tài)庫/框架,并把它們也鏈接到最終結(jié)果以便不需要單獨(dú)添加到用戶項(xiàng)目中,使用“偽”框架

“偽”框架

“偽”框架是破解的“reloacatable object file”(可重定位格式的目標(biāo)文件,

保存著代碼和數(shù)據(jù),適合于和其他的目標(biāo)文件連接到一起,用來創(chuàng)建一個(gè)可執(zhí)行目標(biāo)文件或者是一個(gè)可共享目標(biāo)文件),它可以讓Xcode編譯出類似框架的東西——其實(shí)也是一個(gè)bundle。

“偽框架”模板把整個(gè)過程分為幾個(gè)步驟,用某些腳本去產(chǎn)生一個(gè)真正的靜態(tài)框架(基于靜態(tài)庫而不是reloacatable object file)。而且,框架項(xiàng)目還是把它定義為wrapper.cfbundle類型,一種Xcode中的“二等公民”。

因此它跟“真”靜態(tài)框架一樣可以正常工作,但當(dāng)存在依賴關(guān)系時(shí)就有麻煩了。

依賴問題

如果不使用依賴,只是創(chuàng)建普通的項(xiàng)目是沒有任何問題的。但是如果使用了項(xiàng)目依賴(比如在workspace中),Xcode就悲劇了。當(dāng)你點(diǎn)擊“Link

Binary With

Libraries”下方的’+’按鈕時(shí),“偽框架”無法顯示在列表中。你可以從你的“偽”框架項(xiàng)目的Products下面將它手動(dòng)拖入,但當(dāng)你編輯你的主項(xiàng)目時(shí),會出現(xiàn)警告:

warning: skipping file '/somewhere/MyFramework.framework'

(unexpectedfile type 'wrapper.cfbundle' in Frameworks & Libraries

build phase) 并伴隨“偽”框架中的鏈接錯(cuò)誤。

幸運(yùn)的是,有個(gè)辦法來解決它。你可以在”O(jiān)ther Linker Flags”中用”-framwork”開關(guān)手動(dòng)告訴linker去使用你的框架進(jìn)行鏈接:

-framework MyFramework

警告仍然存在,但起碼能正確鏈接了。

添加其他的庫/框架

如果你加入其他靜態(tài)(不是動(dòng)態(tài))庫/框架到你的“偽”框架項(xiàng)目中,它們將“鏈接”進(jìn)你最終的二進(jìn)制框架文件中。在“真”框架項(xiàng)目中,它們是純引用,而不是鏈接。

你可以在項(xiàng)目中僅僅包含頭文件而不是靜態(tài)庫/框架本身的方式避免這種情況(以便編譯通過)。

“真”框架

“真”框架各個(gè)方面都符合“真”的標(biāo)準(zhǔn)。它是真正的靜態(tài)框架,正如使用蘋果在從Xcode中去除的那個(gè)功能所創(chuàng)建的一樣。

為了能創(chuàng)建真正的靜態(tài)框架項(xiàng)目,你必需在Xcode中安裝一個(gè)xcspec文件。

如果你發(fā)布一個(gè)“真”框架項(xiàng)目(而不是編譯),希望去編譯這個(gè)框架的人必需也安裝xcspec文件(使用本次發(fā)布的安裝腳本),以便Xcode能理解目標(biāo)類型。

注意:如果你正在發(fā)布完全編譯的框架,而不是框架項(xiàng)目,最終用戶并不需要安裝任何東西。 我已經(jīng)提交一個(gè)報(bào)告給蘋果,希望他們在Xcode中更新這個(gè)文件,但那需要一點(diǎn)時(shí)間。

加其他靜態(tài)庫/框架

如果你加入其他靜態(tài)(不是動(dòng)態(tài))庫/框架到你的“真”框架項(xiàng)目,它們只會被引用,而不會象“偽”框架一樣被鏈接到最終的二進(jìn)制文件中。

從早期版本升級

如果你是從Mk6或者更早的版本升級,同時(shí)使用“真”靜態(tài)框架,并且使用Xcode4.2.1以前的版本,請運(yùn)行uninstall_legacy.sh以卸載早期用于Xcode的所有修正。然后再運(yùn)行install.sh,重啟Xcode。如果你使用Xcode4.3以后,只需要運(yùn)行install.sh并重啟Xcode。

安裝

分別運(yùn)行Real Framework目錄或Fake Framework目錄下的install.sh腳本進(jìn)行安裝(或者兩個(gè)你都運(yùn)行)。

重啟Xcode,你將在新項(xiàng)目向?qū)У腇ramework&Library下看到StaticiOS Framework(或者Fake Static iOS Framework)。

卸載請運(yùn)行unistall.sh腳本并重啟Xcode。

創(chuàng)建一個(gè)iOS框架項(xiàng)目

創(chuàng)建新項(xiàng)目。

項(xiàng)目類型選擇Framework&Library下的Static iOS Framework(或者Fake Static iOS Framework)。

選擇“包含單元測試”(可選的)。

在target中加入類、資源等。

凡是其他項(xiàng)目要使用的頭文件,必需聲明為public。進(jìn)入target的Build Phases頁,展開Copy Headers項(xiàng),把需要public的頭文件從Project或Private部分拖拽到Public部分。

編譯你的 iOS 框架

選擇指定target的scheme

修改scheme的Run配置(可選)。Run配置默認(rèn)使用Debug,但在準(zhǔn)備部署的時(shí)候你可能想使用Release。

編譯框架(無論目標(biāo)為iOS device和Simulator都會編譯出相同的二進(jìn)制,因此選誰都無所謂了)。

從Products下選中你的framework,“show in Finder”。

在build目錄下有兩個(gè)文件夾:(yourframework).framework and (your framework).embeddedframework.

如果你的框架只有代碼,沒有資源(比如圖片、腳本、xib、coredata的momd文件等),你可以把(yourframework).framework

分發(fā)給你的用戶就行了。如果還包含有資源,你必需分發(fā)(your framework).embeddedframework給你的用戶。

為什么需要embedded framework?因?yàn)閄code不會查找靜態(tài)框架中的資源,如果你分發(fā)(your framework).framework, 則框架中的所有資源都不會顯示,也不可用。

一個(gè)embedded framework只是一個(gè)framework之外的附加的包,包括了這個(gè)框架的所有資源的符號鏈接。這樣做的目的是讓Xcode能夠找到這些資源。

使用iOS 框架

OS框架和常規(guī)的Mac OS動(dòng)態(tài)框架差不多,只是它是靜態(tài)鏈接的而已。

在你的項(xiàng)目中使用一個(gè)框架,只需把它拖僅你的項(xiàng)目中。在包含頭文件時(shí),記住使用尖括號而不是雙引號括住框架名稱。例如,對于框架MyFramework:

import

使用問題

Headers Not Found

如果Xcode找不到框架的頭文件,你可能是忘記將它們聲明為public了。參考“創(chuàng)建一個(gè)iOS框架項(xiàng)目”第5步。

No Such Product Type

如果你沒有安裝iOS Universal Framework在Xcode,并企圖編譯一個(gè)universal框架項(xiàng)目(對于“真”框架,不是“假”框架),這會導(dǎo)致下列錯(cuò)誤:

target specifies product type

'com.apple.product-type.framework.static',but there's no such product

type for the 'iphonesimulator' platform

為了編譯“真”iOS靜態(tài)框架,Xcode需要做一些改動(dòng),因此為了編譯“真”靜態(tài)框架項(xiàng)目,請?jiān)谒械拈_發(fā)環(huán)境中安裝它(對于使用框架的用戶不需要,只有要編譯框架才需要)。

The selected run destination is not valid for this action

有時(shí),Xcode出錯(cuò)并加載了錯(cuò)誤的active設(shè)置。首先,請嘗試重啟Xcode。如果錯(cuò)誤繼續(xù)存在,Xcode產(chǎn)生了一個(gè)壞的項(xiàng)目(因?yàn)閄code4的一個(gè)bug,任何類型的項(xiàng)目都會出現(xiàn)這個(gè)問題)。如果是這樣,你需要?jiǎng)?chuàng)建一個(gè)新項(xiàng)目重來一遍。

鏈接警告

第一次編譯框架target時(shí),Xcdoe會在鏈接階段報(bào)告找不到文件夾: ld: warning: directory not found

for

option'-L/Users/myself/Library/Developer/Xcode/DerivedData/MyFramework-ccahfoccjqiognaqraesrxdyqcne/Build/Products/Debug-iphoneos'

此時(shí),可以clean并重新編譯target,警告會消除。

Core Data momd not found

對于框架項(xiàng)目和應(yīng)用程序項(xiàng)目,Xcode會以不同的方式編譯momd(托管對象模型文件)。Xcode會簡單地在根目錄創(chuàng)建.mom文件,而不會創(chuàng)建一個(gè).momd目錄(目錄中包含VersionInfo.plist和.mom文件)。

這意味著,當(dāng)從一個(gè)embedded framework的model中實(shí)例化NSManagedObjectModel時(shí),你必需使用.mom擴(kuò)展名作為model的URL,而不是采用.momd擴(kuò)展名。

NSURL *modelURL = [[NSBundle mainBundle]URLForResource:@"MyModel" withExtension:@"mom"];

Unknown class MyClass in Interface Builder file.

由于靜態(tài)框架采用靜態(tài)鏈接,linker會剔除所有它認(rèn)為無用的代碼。不幸的是,linker不會檢查xib文件,因此如果類是在xib中引用,而沒有在O-C代碼中引用,linker將從最終的可執(zhí)行文件中刪除類。這是linker的問題,不是框架的問題(當(dāng)你編譯一個(gè)靜態(tài)庫時(shí)也會發(fā)生這個(gè)問題)。蘋果內(nèi)置框架不會發(fā)生這個(gè)問題,因?yàn)樗麄兪沁\(yùn)行時(shí)動(dòng)態(tài)加載的,存在于iOS設(shè)備固件中的動(dòng)態(tài)庫是不可能被刪除的。

有兩個(gè)解決的辦法:

讓框架的最終用戶關(guān)閉linker的優(yōu)化選項(xiàng),通過在他們的項(xiàng)目的Other Linker Flags中添加-ObjC和-all_load。

在框架的另一個(gè)類中加一個(gè)該類的代碼引用。例如,假設(shè)你有個(gè)MyTextField類,被linker剔除了。假設(shè)你還有一個(gè)MyViewController,它在xib中使用了MyTextField,MyViewController并沒有被剔除。你應(yīng)該這樣做:

在MyTextField中:

(void)forceLinkerLoad_ {}

在MyViewController中:

+(void) initialize { [MyTextField forceLinkerLoad_]; }

他們?nèi)匀恍枰砑?ObjC到linker設(shè)置,但不需要強(qiáng)制all_load了。

這種方法需要你多做一點(diǎn)工作,但卻讓最終用戶避免在使用你的框架時(shí)關(guān)閉linker優(yōu)化(關(guān)閉linker優(yōu)化會導(dǎo)致object文件膨脹)。

unexpected file type 'wrapper.cfbundle' in Frameworks &Libraries build phase

這個(gè)問題發(fā)生在把“假”框架項(xiàng)目作為workspace的依賴,或者把它當(dāng)作子項(xiàng)目時(shí)(“真”框架項(xiàng)目沒有這個(gè)問題)。盡管這種框架項(xiàng)目產(chǎn)生了正確的靜態(tài)框架,但Xcode只能從項(xiàng)目文件中看出這是一個(gè)bundle,因此它在檢查依賴性時(shí)發(fā)出一個(gè)警告,并在linker階段跳過它。

你可以手動(dòng)添加一個(gè)命令讓linker在鏈接階段能正確鏈接。在依賴你的靜態(tài)框架的項(xiàng)目的OtherLinker Flags中加入:

-framework MyFramework

警告仍然存在, 但不會導(dǎo)致鏈接失敗。

Libraries being linked or not being linked into the finalframework

很不幸, “真”框架和“假”框架模板在處理引入的靜態(tài)庫/框架的工作方式不同的。

“真”框架模板采用正常的靜態(tài)庫生成步驟,不會鏈接其他靜態(tài)庫/框架到最終生產(chǎn)物中。

“假”框架模板采用“欺騙”Xcode的手段,讓它認(rèn)為是在編譯一個(gè)可重定位格式的目標(biāo)文件,在鏈接階段就如同編譯一個(gè)可執(zhí)行文件,把所有的靜態(tài)代碼文件鏈接到最終生成物中(盡管不會檢查是否確實(shí)目標(biāo)代碼)。為了實(shí)現(xiàn)象“真”框架一樣的效果,你可以只包含庫/框架的頭文件到你的項(xiàng)目中,而不需要包含庫/框架本身。

Unrecognized selector in (some class with a category method)

如果你的靜態(tài)庫或靜態(tài)框架包含了一個(gè)模塊(只在類別代碼中聲明,沒有類實(shí)現(xiàn)),linker會搞不清楚,并把代碼從二進(jìn)制文件中剔除。因?yàn)樵谧罱K生成的文件中沒有這個(gè)方法,所以當(dāng)調(diào)用這個(gè)類別中定義的方法時(shí),會報(bào)一個(gè)“unrecognizedselector”異常。

要解決這個(gè),在包含這個(gè)類別的模塊代碼中加一個(gè)“假的”類。linker發(fā)現(xiàn)存在完整的O-C類,會將類別代碼鏈接到模塊。

我寫了一個(gè)頭文件 LoadableCategory.h,以減輕這個(gè)工作量:

import "SomeConcreteClass+MyAdditions.h"

import "LoadableCategory.h"

MAKE_CATEGORIES_LOADABLE(SomeConcreteClass_MyAdditions);

@implementation SomeConcreteClass(MyAdditions) ... @end

在使用這個(gè)框架時(shí),仍然還需要在Build Setting的Other Linker Flags中加入-ObjC。

執(zhí)行任何代碼前單元測試崩潰

如果你在Xcode4.3中創(chuàng)建靜態(tài)框架(或庫)target時(shí),勾選了“withunit tests”,當(dāng)你試圖運(yùn)行單元測試時(shí),它會崩潰:

Thread 1: EXC_BAD_ACCESS (code=2, address=0x0) 0 0x00000000 --- 15 dyldbootstrap:start(...)

這是lldb中的一個(gè)bug。你可以用GDB來運(yùn)行單元測試。編輯scheme,選擇Test,在Info標(biāo)簽中將調(diào)試器Debugger從LLDB改為GDB。

轉(zhuǎn)載自:https://www.gitbook.com/book/leon_lizi/-framework-/details

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容