靜態庫與動態庫的區別
首先來看什么是庫,庫(Library)說白了就是一段編譯好的二進制代碼,加上頭文件就可以供別人使用。
什么時候我們會用到庫呢?一種情況是某些代碼需要給別人使用,但是我們不希望別人看到源碼,就需要以庫的形式進行封裝,只暴露出頭文件。另外一種情況是,對于某些不會進行大的改動的代碼,我們想減少編譯的時間,就可以把它打包成庫,因為庫是已經編譯好的二進制了,編譯的時候只需要
Link 一下,不會浪費編譯時間。
上面提到庫在使用的時候需要 Link,Link 的方式有兩種,靜態和動態,于是便產生了靜態庫和動態庫。
靜態庫
靜態庫即靜態鏈接庫(Windows 下的 .lib,Linux 和 Mac 下的 .a)。之所以叫做靜態,是因為靜態庫在編譯的時候會被直接拷貝一份,復制到目標程序里,這段代碼在目標程序里就不會再改變了。
靜態庫的好處很明顯,編譯完成之后,庫文件實際上就沒有作用了。目標程序沒有外部依賴,直接就可以運行。當然其缺點也很明顯,就是會使用目標程序的體積增大。
動態庫
動態庫即動態鏈接庫(Windows 下的 .dll,Linux 下的 .so,Mac 下的 .dylib/.tbd)。與靜態庫相反,動態庫在編譯時并不會被拷貝到目標程序中,目標程序中只會存儲指向動態庫的引用。等到程序運行時,動態庫才會被真正加載進來。
動態庫的優點是,不需要拷貝到目標程序中,不會影響目標程序的體積,而且同一份庫可以被多個程序使用(因為這個原因,動態庫也被稱作共享庫)。同時,編譯時才載入的特性,也可以讓我們隨時對庫進行替換,而不需要重新編譯代碼。動態庫帶來的問題主要是,動態載入會帶來一部分性能損失,使用動態庫也會使得程序依賴于外部環境。如果環境缺少動態庫或者庫的版本不正確,就會導致程序無法運行(Linux 下喜聞樂見的 lib not found 錯誤)。
iOS Framework
除了上面提到的 .a 和 .dylib/.tbd 之外,Mac OS/iOS 平臺還可以使用 Framework。Framework 實際上是一種打包方式,將庫的二進制文件,頭文件和有關的資源文件打包到一起,方便管理和分發。
在 iOS 8 之前,iOS 平臺不支持使用動態 Framework,開發者可以使用的 Framework 只有蘋果自家的 UIKit.Framework,Foundation.Framework 等。這種限制可能是出于安全的考慮(見這里的討論)。換一個角度講,因為 iOS 應用都是運行在沙盒當中,不同的程序之間不能共享代碼,同時動態下載代碼又是被蘋果明令禁止的,沒辦法發揮出動態庫的優勢,實際上動態庫也就沒有存在的必要了。
由于上面提到的限制,開發者想要在 iOS 平臺共享代碼,唯一的選擇就是打包成靜態庫 .a 文件,同時附上頭文件(例如微信的SDK)。但是這樣的打包方式不夠方便,使用時也比較麻煩,大家還是希望共享代碼都能能像 Framework 一樣,直接扔到工程里就可以用。于是人們想出了各種奇技淫巧去讓 Xcode Build 出 iOS 可以使用的 Framework,具體做法參考這里和這里,這種方法產生的 Framework 還有 “偽”(Fake) Framework 和 “真”(Real) Framework 的區別。
iOS 8/Xcode 6 推出之后,iOS 平臺添加了動態庫的支持,同時 Xcode 6 也原生自帶了 Framework 支持(動態和靜態都可以),上面提到的的奇技淫巧也就沒有必要了(新的做法參考這里)。為什么 iOS 8 要添加動態庫的支持?唯一的理由大概就是 Extension 的出現。Extension 和 App 是兩個分開的可執行文件,同時需要共享代碼,這種情況下動態庫的支持就是必不可少的了。但是這種動態 Framework 和系統的 UIKit.Framework 還是有很大區別。系統的 Framework 不需要拷貝到目標程序中,我們自己做出來的 Framework 哪怕是動態的,最后也還是要拷貝到 App 中(App 和 Extension 的 Bundle 是共享的),因此蘋果又把這種 Framework 稱為Embedded Framework。
Swift 支持
跟著 iOS8 / Xcode 6 同時發布的還有 Swift。如果要在項目中使用外部的代碼,可選的方式只有兩種,一種是把代碼拷貝到工程中,另一種是用動態 Framework。使用靜態庫是不支持的。
造成這個問題的原因主要是 Swift 的運行庫沒有被包含在 iOS 系統中,而是會打包進 App 中(這也是造成 Swift App 體積大的原因),靜態庫會導致最終的目標程序中包含重復的運行庫(這是蘋果自家的解釋)。同時拷貝 Runtime 這種做法也會導致在純 ObjC 的項目中使用 Swift 庫出現問題。蘋果聲稱等到 Swift 的 Runtime 穩定之后會被加入到系統當中,到時候這個限制就會被去除了(參考這個問題的問題描述,也是來自蘋果自家文檔)。
CocoaPods 的做法
在純 ObjC 的項目中,CocoaPods 使用編譯靜態庫 .a 方法將代碼集成到項目中。在 Pods 項目中的每個 target 都對應這一個 Pod 的靜態庫。不過在編譯過程中并不會真的產出 .a 文件。如果需要 .a 文件的話,可以參考這里,或者使用CocoasPods-Packager這個插件。
當不想發布代碼的時候,也可以使用 Framework 發布 Pod,CocoaPods 提供了vendored_framework選項來使用第三方 Framework,具體的做法可以參考這里和這里。
對于 Swift 項目,CocoaPods 提供了動態 Framework 的支持,通過use_frameworks!選項控制。
更多有關代碼分發的擴展資料可以參考這篇博客: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
http://blog.cocoapods.org/CocoaPods-0.36/
轉載自:https://skyline75489.github.io/post/2015-8-14_ios_static_dynamic_framework_learning.html
使用靜態庫的好處
1,模塊化,分工合作
2,避免少量改動經常導致大量的重復編譯連接
3,也可以重用,注意不是共享使用
動態庫使用有如下好處:
1使用動態庫,可以將最終可執行文件體積縮小
2使用動態庫,多個應用程序共享內存中得同一份庫文件,節省資源
3使用動態庫,可以不重新編譯連接可執行程序的前提下,更新動態庫文件達到更新應用程序的目的。
從1可以得出,將整個應用程序分模塊,團隊合作,進行分工,影響比較小。
等其他好處,
從2可以看出,其實動態庫應該叫共享庫,那么從這個意義上來說,蘋果禁止iOS開發中使用動態庫就可以理解了:
因為在現在的iPhone,iPodTouch,iPad上面程序都是單進程的,也就是某一時刻只有一個進程在運行,那么你寫個共享庫,
----共享給誰?(你使用的時候只有你一個應用程序存在,其他的應該被掛起了,即便是可以同時多個進程運行,別人能使用你的共享庫里的東西嗎?你這個是給你自己的程序定制的。)
----目前蘋果的AppStore不支持模塊更新,無法更新某個單獨文件(除非自己寫一個更新機制:有自己的服務端放置最新動態庫文件)
至于蘋果為啥禁止ios開發使用動態庫我就猜到上面倆原因
深入理解iPhone靜態庫
在實際的編程過程中,通常會把一些公用函數制成函數庫,供其它程序使用,一則提搞了代碼的復用;二則提搞了核心技術的保密程度。所以在實際的項目開發中,經常會使用到函數庫,函數庫分為靜態庫和動態庫兩種。和多數人所熟悉的動態語言和靜態語言一樣,這里的所謂靜態和動態是相對編譯期和運行期的:靜態庫在程序編譯時會被鏈接到目標代碼中,程序運行時將不再需要改靜態庫;而動態庫在程序編譯時并不會被鏈接到目標代碼中,只是在程序運行時才被載入,因為在程序運行期間還需要動態庫的存在。
iPhone官方只支持靜態庫聯編。
深入理解framework(框架,其實相當于靜態框架,不是動態庫)
打包framework還是一個比較重要的功能,可以用來做一下事情:
(1)封裝功能模塊,比如有比較成熟的功能模塊封裝成一個包,然后以后自己或其他同事用起來比較方便。
(2)封裝項目,有時候會遇到這個情況,就是一家公司找了兩個開發公司做兩個項目,然后要求他們的項目中的一個嵌套進另一個項目,此時也可以把唄嵌套的項目打包成framework放進去,這樣比較方便。
我們為什么需要框架(Framework)?
要想用一種開發者友好的方式共享庫是很麻煩的。你不僅僅需要包含庫本身,還要加入所有的頭文件,資源等等。
蘋果解決這個問題的方式是框架(framework)。基本上,這是含有固定結構并包含了引用該庫時所必需的所有東西的文件夾。不幸的是,iOS禁止所有的動態庫。同時,蘋果也從Xcode中移除了創建靜態iOS框架的功能。
Xcode仍然可以支持創建框架的功能,重啟這個功能,我們需要對Xcode做一些小小的改動。
把代碼封裝在靜態框架是被app store所允許的。盡管形式不同,本質上它仍然是一種靜態庫。
框架(Framework)的類別
大部分框架都是動態鏈接庫的形式。因為只有蘋果才能在iOS設備上安裝動態庫,所以我們無法創建這種類型的框架。
靜態鏈接庫和動態庫一樣,只不過它是在編譯時鏈接二進制代碼,因此使用靜態庫不會有動態庫那樣的問題(即除了蘋果誰也不能在iOS上使用動態庫)。
“偽”框架是通過破解Xcode的目標Bundle(使用某些腳本)來實現的。它在表面上以及使用時跟靜態框架并無區別。“偽”框架項目的功能幾乎和真實的框架項目沒有區別(不是全部)。
“嵌入”框架是靜態框架的一個包裝,以便Xcode能獲取框架內的資源(圖片、plist、nib等)。
本次發布包括了創建靜態框架和“偽”框架的模板,以及二者的“嵌入”框架。
用哪一種模板?
本次發布有兩個模板,每個模板都有“強”“弱”兩個類別。你可以選擇最適合一種(或者兩種都安裝上)。
最大的不同是Xcode不能創建“真”框架,除非你安裝靜態框架文件xcspec在Xcode中。這真是一個遺憾(這個文件是給項目使用的,而不是框架要用的)。
簡單第
簡單說,你可以這樣決定用哪一種模板:
如果你不想修改Xcode,那么請使用“偽”框架版本
如果你只是想共享二進制(不是項目),兩種都可以
如果你想把框架共享給不想修改Xcode的開發者,使用“偽”框架版本
如果你想把框架共享給修改過Xcode的開發者,使用“真”框架版本
如果你想把框架項目作為另一個項目的依賴(通過workspace或者子項目的方式),請使用“真”框架(或者“偽”框架,使用-framework——見后)
如果你想在你的框架項目中加入其他靜態庫/框架,并把它們也鏈接到最終結果以便不需要單獨添加到用戶項目中,使用“偽”框架
“偽”框架
“偽”框架是破解的“reloacatable object file”(可重定位格式的目標文件,
保存著代碼和數據,適合于和其他的目標文件連接到一起,用來創建一個可執行目標文件或者是一個可共享目標文件),它可以讓Xcode編譯出類似框架的東西——其實也是一個bundle。
“偽框架”模板把整個過程分為幾個步驟,用某些腳本去產生一個真正的靜態框架(基于靜態庫而不是reloacatable object file)。而且,框架項目還是把它定義為wrapper.cfbundle類型,一種Xcode中的“二等公民”。
因此它跟“真”靜態框架一樣可以正常工作,但當存在依賴關系時就有麻煩了。
依賴問題
如果不使用依賴,只是創建普通的項目是沒有任何問題的。但是如果使用了項目依賴(比如在workspace中),Xcode就悲劇了。當你點擊“Link
Binary With
Libraries”下方的’+’按鈕時,“偽框架”無法顯示在列表中。你可以從你的“偽”框架項目的Products下面將它手動拖入,但當你編輯你的主項目時,會出現警告:
warning: skipping file '/somewhere/MyFramework.framework'
(unexpectedfile type 'wrapper.cfbundle' in Frameworks & Libraries
build phase)
并伴隨“偽”框架中的鏈接錯誤。
幸運的是,有個辦法來解決它。你可以在”Other Linker Flags”中用”-framwork”開關手動告訴linker去使用你的框架進行鏈接:
-framework MyFramework
警告仍然存在,但起碼能正確鏈接了。
添加其他的庫/框架
如果你加入其他靜態(不是動態)庫/框架到你的“偽”框架項目中,它們將“鏈接”進你最終的二進制框架文件中。在“真”框架項目中,它們是純引用,而不是鏈接。
你可以在項目中僅僅包含頭文件而不是靜態庫/框架本身的方式避免這種情況(以便編譯通過)。
“真”框架
“真”框架各個方面都符合“真”的標準。它是真正的靜態框架,正如使用蘋果在從Xcode中去除的那個功能所創建的一樣。
為了能創建真正的靜態框架項目,你必需在Xcode中安裝一個xcspec文件。
如果你發布一個“真”框架項目(而不是編譯),希望去編譯這個框架的人必需也安裝xcspec文件(使用本次發布的安裝腳本),以便Xcode能理解目標類型。
注意:如果你正在發布完全編譯的框架,而不是框架項目,最終用戶并不需要安裝任何東西。
我已經提交一個報告給蘋果,希望他們在Xcode中更新這個文件,但那需要一點時間.OpenRadarlink here
加其他靜態庫/框架
如果你加入其他靜態(不是動態)庫/框架到你的“真”框架項目,它們只會被引用,而不會象“偽”框架一樣被鏈接到最終的二進制文件中。
從早期版本升級
如果你是從Mk6或者更早的版本升級,同時使用“真”靜態框架,并且使用Xcode4.2.1以前的版本,請運行uninstall_legacy.sh以卸載早期用于Xcode的所有修正。然后再運行install.sh,重啟Xcode。如果你使用Xcode4.3以后,只需要運行install.sh并重啟Xcode。
安裝
分別運行Real Framework目錄或Fake Framework目錄下的install.sh腳本進行安裝(或者兩個你都運行)。
重啟Xcode,你將在新項目向導的Framework&Library下看到StaticiOS Framework(或者Fake Static iOS Framework)。
卸載請運行unistall.sh腳本并重啟Xcode。
創建一個iOS框架項目
創建新項目。
項目類型選擇Framework&Library下的Static iOS Framework(或者Fake Static iOS Framework)。
選擇“包含單元測試”(可選的)。
在target中加入類、資源等。
凡是其他項目要使用的頭文件,必需聲明為public。進入target的Build Phases頁,展開Copy Headers項,把需要public的頭文件從Project或Private部分拖拽到Public部分。
編譯你的 iOS 框架
選擇指定target的scheme
修改scheme的Run配置(可選)。Run配置默認使用Debug,但在準備部署的時候你可能想使用Release。
編譯框架(無論目標為iOS device和Simulator都會編譯出相同的二進制,因此選誰都無所謂了)。
從Products下選中你的framework,“show in Finder”。
在build目錄下有兩個文件夾:(yourframework).frameworkand(your framework).embeddedframework.
如果你的框架只有代碼,沒有資源(比如圖片、腳本、xib、coredata的momd文件等),你可以把(yourframework).framework分發給你的用戶就行了。如果還包含有資源,你必需分發(your framework).embeddedframework給你的用戶。
為什么需要embedded framework?因為Xcode不會查找靜態框架中的資源,如果你分發(your framework).framework, 則框架中的所有資源都不會顯示,也不可用。
一個embedded framework只是一個framework之外的附加的包,包括了這個框架的所有資源的符號鏈接。這樣做的目的是讓Xcode能夠找到這些資源。
使用iOS 框架
iOS框架和常規的Mac OS動態框架差不多,只是它是靜態鏈接的而已。
在你的項目中使用一個框架,只需把它拖僅你的項目中。在包含頭文件時,記住使用尖括號而不是雙引號括住框架名稱。例如,對于框架MyFramework:
#import
使用問題
Headers Not Found
如果Xcode找不到框架的頭文件,你可能是忘記將它們聲明為public了。參考“創建一個iOS框架項目”第5步。
No Such Product Type
如果你沒有安裝iOS Universal Framework在Xcode,并企圖編譯一個universal框架項目(對于“真”框架,不是“假”框架),這會導致下列錯誤:
target specifies product type
'com.apple.product-type.framework.static',but there's no such product
type for the 'iphonesimulator' platform
為了編譯“真”iOS靜態框架,Xcode需要做一些改動,因此為了編譯“真”靜態框架項目,請在所有的開發環境中安裝它(對于使用框架的用戶不需要,只有要編譯框架才需要)。
The selected run destination is not valid for this action
有時,Xcode出錯并加載了錯誤的active設置。首先,請嘗試重啟Xcode。如果錯誤繼續存在,Xcode產生了一個壞的項目(因為Xcode4的一個bug,任何類型的項目都會出現這個問題)。如果是這樣,你需要創建一個新項目重來一遍。
鏈接警告
第一次編譯框架target時,Xcdoe會在鏈接階段報告找不到文件夾:
ld: warning: directory not found for
option'-L/Users/myself/Library/Developer/Xcode/DerivedData/MyFramework-ccahfoccjqiognaqraesrxdyqcne/Build/Products/Debug-iphoneos'
此時,可以clean并重新編譯target,警告會消除。
Core Data momd not found
對于框架項目和應用程序項目,Xcode會以不同的方式編譯momd(托管對象模型文件)。Xcode會簡單地在根目錄創建.mom文件,而不會創建一個.momd目錄(目錄中包含VersionInfo.plist和.mom文件)。
這意味著,當從一個embedded framework的model中實例化NSManagedObjectModel時,你必需使用.mom擴展名作為model的URL,而不是采用.momd擴展名。
NSURL *modelURL = [[NSBundle mainBundle]URLForResource:@"MyModel" withExtension:@"mom"];
Unknown class MyClass in Interface Builder file.
由于靜態框架采用靜態鏈接,linker會剔除所有它認為無用的代碼。不幸的是,linker不會檢查xib文件,因此如果類是在xib中引用,而沒有在O-C代碼中引用,linker將從最終的可執行文件中刪除類。這是linker的問題,不是框架的問題(當你編譯一個靜態庫時也會發生這個問題)。蘋果內置框架不會發生這個問題,因為他們是運行時動態加載的,存在于iOS設備固件中的動態庫是不可能被刪除的。
有兩個解決的辦法:
讓框架的最終用戶關閉linker的優化選項,通過在他們的項目的Other Linker Flags中添加-ObjC和-all_load。
在框架的另一個類中加一個該類的代碼引用。例如,假設你有個MyTextField類,被linker剔除了。假設你還有一個MyViewController,它在xib中使用了MyTextField,MyViewController并沒有被剔除。你應該這樣做:
在MyTextField中:
+ (void)forceLinkerLoad_ {}
在MyViewController中:
+(void) initialize {?????[MyTextField forceLinkerLoad_]; }
他們仍然需要添加-ObjC到linker設置,但不需要強制all_load了。
第2種方法需要你多做一點工作,但卻讓最終用戶避免在使用你的框架時關閉linker優化(關閉linker優化會導致object文件膨脹)。
unexpected file type 'wrapper.cfbundle' in Frameworks &Libraries build phase
這個問題發生在把“假”框架項目作為workspace的依賴,或者把它當作子項目時(“真”框架項目沒有這個問題)。盡管這種框架項目產生了正確的靜態框架,但Xcode只能從項目文件中看出這是一個bundle,因此它在檢查依賴性時發出一個警告,并在linker階段跳過它。
你可以手動添加一個命令讓linker在鏈接階段能正確鏈接。在依賴你的靜態框架的項目的OtherLinker Flags中加入:
-framework MyFramework
警告仍然存在, 但不會導致鏈接失敗。
Libraries being linked or not being linked into the finalframework
很不幸, “真”框架和“假”框架模板在處理引入的靜態庫/框架的工作方式不同的。
“真”框架模板采用正常的靜態庫生成步驟,不會鏈接其他靜態庫/框架到最終生產物中。
“假”框架模板采用“欺騙”Xcode的手段,讓它認為是在編譯一個可重定位格式的目標文件,在鏈接階段就如同編譯一個可執行文件,把所有的靜態代碼文件鏈接到最終生成物中(盡管不會檢查是否確實目標代碼)。為了實現象“真”框架一樣的效果,你可以只包含庫/框架的頭文件到你的項目中,而不需要包含庫/框架本身。
Unrecognized selector in (some class with a category method)
如果你的靜態庫或靜態框架包含了一個模塊(只在類別代碼中聲明,沒有類實現),linker會搞不清楚,并把代碼從二進制文件中剔除。因為在最終生成的文件中沒有這個方法,所以當調用這個類別中定義的方法時,會報一個“unrecognizedselector”異常。
要解決這個,在包含這個類別的模塊代碼中加一個“假的”類。linker發現存在完整的O-C類,會將類別代碼鏈接到模塊。
我寫了一個頭文件?LoadableCategory.h,以減輕這個工作量:
#import "SomeConcreteClass+MyAdditions.h"
#import
"LoadableCategory.h"??MAKE_CATEGORIES_LOADABLE(SomeConcreteClass_MyAdditions);
@implementation SomeConcreteClass(MyAdditions)
...
@end
在使用這個框架時,仍然還需要在Build Setting的Other Linker Flags中加入-ObjC。
執行任何代碼前單元測試崩潰
如果你在Xcode4.3中創建靜態框架(或庫)target時,勾選了“withunit tests”,當你試圖運行單元測試時,它會崩潰:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x0) 0 0x00000000 --- 15 dyldbootstrap:start(...)
這是lldb中的一個bug。你可以用GDB來運行單元測試。編輯scheme,選擇Test,在Info標簽中將調試器Debugger從LLDB改為GDB。
原文鏈接:http://www.lxweimin.com/p/4666ce7dc622
前言
1.靜態庫和動態庫有什么異同?
靜態庫:鏈接時完整地拷貝至可執行文件中,被多次使用就有多份冗余拷貝。利用靜態函數庫編譯成的文件比較大,因為整個
函數庫的所有數據都會被整合進目標代碼中,他的優點就顯而易見了,即編譯后的執行程序不需要外部的函數庫支持,因為所有使用的函數都已經被編譯進去了。當然這也會成為他的缺點,因為如果靜態函數庫改變了,那么你的程序必須重新編譯。
動態庫:鏈接時不復制,程序運行時由系統動態加載到內存,供程序調用,系統只加載一次,多個程序共用,節省內存。由于函數庫沒有被整合進你的程序,而是程序運行時動態的申請并調用,所以程序的運行環境中必須提供相應的庫。動態函數庫的改變并不影響你的程序,所以動態函數庫的升級比較方便。
靜態庫和動態庫都是閉源庫,只能拿來滿足某個功能的使用,不會暴露內部具體的代碼信息,而從github上下載的第三方庫大多是開源庫
靜態庫和動態庫都是由*.o目標文件生成
使用靜態庫的好處
模塊化,分工合作
避免少量改動經常導致大量的重復編譯連接
也可以重用,注意不是共享使用
動態庫使用有如下好處:
可以將最終可執行文件體積縮小
多個應用程序共享內存中得同一份庫文件,節省資源
可以不重新編譯連接可執行程序的前提下,更新動態庫文件達到更新應用程序的目的。
將整個應用程序分模塊,團隊合作,進行分工,影響比較小。
其實動態庫應該叫共享庫,那么從這個意義上來說,蘋果禁止iOS開發中使用動態庫就可以理解了: 因為在現在的iPhone,iPodTouch,iPad上面程序都是單進程的,也就是某一時刻只有一個進程在運行,那么你寫個共享庫
----共享給誰?(你使用的時候只有你一個應用程序存在,其他的應該被掛起了,即便是可以同時多個進程運行,別人能使用你的共享庫里的東西嗎?你這個是給你自己的程序定制的。)
----目前蘋果的AppStore不支持模塊更新,無法更新某個單獨文件(除非自己寫一個更新機制:有自己的服務端放置最新動態庫文件)
至于蘋果為啥禁止ios開發使用動態庫我就猜到上面倆原因
2.這兩種庫都有哪些文件格式?
靜態庫:.a和.framework (windows:.lib , linux: .a)
動態庫:.dylib和.framework(系統提供給我們的framework都是動態庫!)(windows:.dll , linux: .so)
注意:兩者都有framework的格式,但是當你創建一個framework文件時,系統默認是動態庫的格式,如果想做成靜態庫,需要在buildSetting中將Mach-O Type選項設置為Static Library就行了!
3..a文件和.framework文件的區別?
.a是一個純二進制文件,不能直接拿來使用,需要配合頭文件、資源文件一起使用。
將靜態庫打包的時候,只能打包代碼資源,圖片、本地json文件和xib等資源文件無法打包進去,使用.a靜態庫的時候需要三個組成部分:.a文件+需要暴露的頭文件+資源文件;
.framework中除了有二進制文件之外還有資源文件,可以拿來直接使用。
4.制作靜態庫需要注意的幾點:
注意理解:無論是.a靜態庫還.framework靜態庫,我們需要的都是二進制文件+.h+其它資源文件的形式,不同的是,.a本身就是二進制文件,需要我們自己配上.h和其它文件才能使用,而.framework本身已經包含了.h和其它文件,可以直接使用。
圖片資源的處理:兩種靜態庫,一般都是把圖片文件單獨的放在一個.bundle文件中,一般.bundle的名字和.a或.framework的名字相同。.bundle文件很好弄,新建一個文件夾,把它改名為.bundle就可以了,右鍵,顯示包內容可以向其中添加圖片資源。
category是我們實際開發項目中經常用到的,把category打成靜態庫是沒有問題的,但是在用這個靜態庫的工程中,調用category中的方法時會有找不到該方法的運行時錯誤(selector
not recognized),解決辦法是:在使用靜態庫的工程中配置other linkerflags的值為-ObjC。
如果一個靜態庫很復雜,需要暴露的.h比較多的話,就可以在靜態庫的內部創建一個.h文件(一般這個.h文件的名字和靜態庫的名字相同),然后把所有需要暴露出來的.h文件都集中放在這個.h文件中,而那些原本需要暴露的.h都不需要再暴露了,只需要把.h暴露出來就可以了。
5.framework動態庫的主要作用:
framework本來是蘋果專屬的內部提供的動態庫文件格式,但是自從2014年WWDC之后,開發者也可以自定義創建framework實現動態更新(繞過apple
store審核,從服務器發布更新版本)的功能,這與蘋果限定的上架的app必須經過apple
store的審核制度是沖突的,所以含有自定義的framework的app是無法在商店上架的,但是如果開發的是企業內部應用,就可以考慮嘗試使用動態更新技術來將多個獨立的app或者功能模塊集成在一個app上面!(筆者開發的就是企業內部使用的app,我們將企業官網中的板塊開發成4個獨立的app,然后將其改造為framework文件集成在一款平臺級的app當中進行使用)
目前 iOS 上的動態更新方案主要有以下 4 種:
HTML 5
lua(wax)hotpatch
react native
framework
前面三種都是通過在應用內搭建一個運行環境來實現動態更新(HTML 5 是原生支持),在用戶體驗、與系統交互上有一定的限制,對開發者的要求也更高(至少得熟悉 lua 或者 js)。
使用 framework 的方式來更新可以不依賴第三方庫,使用原生的 OC/Swift 來開發,體驗更好,開發成本也更低。
由于 Apple 不希望開發者繞過 App Store 來更新 app,因此只有對于不需要上架的應用,才能以 framework 的方式實現 app 的更新。
主要思路
將 app 中的某個模塊(比如一個 tab)的內容獨立成一個 framework 的形式動態加載,在 app 的 main bundle 中,當 app 啟動時從服務器上下載新版本的 framework 并加載即可達到動態更新的目的。
實戰
創建一個普通工程 DynamicUpdateDemo,其包含一個 framework 子工程 Module。也可以將 Module 創建為獨立的工程,創建工程的過程不再贅述。
依賴
在主工程的 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 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 中尋找默認的
framework。 其中的關鍵是利用 OC 的動態特性 NSClassFromString 和 performSelector 加載
framework 的類并且執行其方法。
framework 和 host 工程資源共用
第方三庫
Class XXX is implemented in both XXX and XXX. One of the two will be used. Which one is undefined.
這是當 framework 工程和 host 工程鏈接了相同的第三方庫或者類造成的。
為了讓打出的 framework 中不包含 host 工程中已包含的三方庫(如 cocoapods 工程編譯出的 .a 文件),可以這樣:
刪除 Build Phases > Link Binary With Libraries 中的內容(如有)。此時編譯會提示三方庫中包含的符號找不到。
在 framework 的 Build Settings > Other Linker Flags 添加 -undefined dynamic_lookup。必須保證 host 工程編譯出的二進制文件中包含這些符號。
類文件
嘗試過在 framework 中引用 host 工程中已有的文件,通過 Build Settings > Header Search
Paths 中添加相應的目錄,Xcode 在編譯的時候可以成功(因為添加了 -undefined dynamic_lookup),并且
Debug 版本是可以正常運行的,但是 Release 版本動態加載時會提示找不到符號:
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.}
因為 Debug 版本暴露了所有自定義類的符號以便于調試,因此你的 framework 可以找到相應的符號,而 Release 版本則不會。
目前能想到的方法只有將相同的文件拷貝一份到 framework 工程里,并且更改類名。
訪問 framework 中的圖片
在 storyboard/xib 中可以直接訪問圖片,代碼中訪問的方法如下:
UIImage *image = [UIImage imageNamed:@"YourFramework.framework/imageName"]
注意:使用代碼方式訪問的圖片不可以放在 xcassets 中,否則得到的將是 nil。并且文件名必須以 @2x/@3x 結尾,大小寫敏感。因為 imageNamed: 默認在 main bundle 中查找圖片。
常見錯誤
Architecture
dlopen(/path/to/framework, 9): no suitable image found.? Did find:
/path/to/framework: mach-o, but wrong architecture
這是說 framework 不支持當前機器的架構。 通過
lipo -info /path/to/MyFramework.framework/MyFramework
可以查看 framework 支持的 CPU 架構。
碰到這種錯誤,一般是因為編譯 framework 的時候,scheme 選擇的是模擬器,應該選擇iOS Device。
此外,如果沒有選擇iOS Device,編譯完成后,Products 目錄下的 .framework 文件名會一直是紅色,只有在 Derived Data 目錄下才能找到編譯生成的 .framework 文件。
關于other linker flag
使用靜態庫或者動態庫的時候極易發生鏈接錯誤,而且大多發生在加載framework中category的情況!根本原因在于Objective-C的鏈接器并不會為每個方法建立符號表,而是僅僅為類建立了符號表。這樣的話,如果靜態庫中定義了已存在的一個類的分類,鏈接器就會以為這個類已經存在,不會把分類和核心類的代碼合起來。這樣的話,在最后的可執行文件中,就會缺少分類里的代碼,這樣函數調用就失敗了。常見的設置方法就是在other linker flag中添加一個語句:-all_load,但是這樣也并不是萬能的,具體解析請參考鏈接:http://my.oschina.net/u/728866/blog/194741
注意:當flag里面添加了注釋卻還是無法使用的時候,可能報flag與bitcode沖突的問題尤其是第三方庫可能和bitcode沖突),這樣的話就需要將bitcode設置為NO!
bitcode的具體作用不做詳談,可參考:http://www.lxweimin.com/p/3e1b4e2d06c6
簽名
系統在加載動態庫時,會檢查 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() error 1
此外,如果用來打包的證書是 iOS 8 發布之前生成的,則打出的包驗證的時候會沒有 TeamIdentifier 這一項。這時在加載 framework 的時候會報下面的錯誤:
[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 命令來驗證。
codesign -dv /path/to/YourApp.app
如果證書太舊,輸出的結果如下:
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 這個動態庫的時候也會遇到這個問題,對此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 舊的證書會使所有用戶已經安裝的,用該證書打包的 app 無法運行。
等等,我們就跪在這里了嗎?!
現在企業證書的有效期是三年,當證書過期時,其打包的應用就不能運行,那企業應用怎么來更替證書呢?
Apple 為每個賬號提供了兩個證書,這兩個證書可以同時生效,這樣在正在使用的證書過期之前,可以使用另外一個證書打包發布,讓用戶升級到新版本。
也就是說,可以使用另外一個證書來打包應用,并且可以覆蓋安裝使用舊證書打包的應用。詳情可以看Apple 文檔。
深入理解iPhone靜態庫
在實際的編程過程中,通常會把一些公用函數制成函數庫,供其它程序使用,一則提搞了代碼的復用;二則提搞了核心技術的保密程度。所以在實際的項目開發中,經常會使用到函數庫,函數庫分為靜態庫和動態庫兩種。和多數人所熟悉的動態語言和靜態語言一樣,這里的所謂靜態和動態是相對編譯期和運行期的:靜態庫在程序編譯時會被鏈接到目標代碼中,程序運行時將不再需要改靜態庫;而動態庫在程序編譯時并不會被鏈接到目標代碼中,只是在程序運行時才被載入,因為在程序運行期間還需要動態庫的存在。
深入理解framework(框架,相當于靜態框架,不是動態庫)
打包framework還是一個比較重要的功能,可以用來做一下事情:
封裝功能模塊,比如有比較成熟的功能模塊封裝成一個包,然后以后自己或其他同事用起來比較方便。
封裝項目,有時候會遇到這個情況,就是一家公司找了兩個開發公司做兩個項目,然后要求他們的項目中的一個嵌套進另一個項目,此時也可以把唄嵌套的項目打包成framework放進去,這樣比較方便。
我們為什么需要框架(Framework)?
要想用一種開發者友好的方式共享庫是很麻煩的。你不僅僅需要包含庫本身,還要加入所有的頭文件,資源等等。
蘋果解決這個問題的方式是框架(framework)。基本上,這是含有固定結構并包含了引用該庫時所必需的所有東西的文件夾。不幸的是,iOS禁止所有的動態庫。同時,蘋果也從Xcode中移除了創建靜態iOS框架的功能。
Xcode仍然可以支持創建框架的功能,重啟這個功能,我們需要對Xcode做一些小小的改動。
把代碼封裝在靜態框架是被app store所允許的。盡管形式不同,本質上它仍然是一種靜態庫。
框架(Framework)的類別
大部分框架都是動態鏈接庫的形式。因為只有蘋果才能在iOS設備上安裝動態庫,所以我們無法創建這種類型的框架。
靜態鏈接庫和動態庫一樣,只不過它是在編譯時鏈接二進制代碼,因此使用靜態庫不會有動態庫那樣的問題(即除了蘋果誰也不能在iOS上使用動態庫)。
“偽”框架是通過破解Xcode的目標Bundle(使用某些腳本)來實現的。它在表面上以及使用時跟靜態框架并無區別。“偽”框架項目的功能幾乎和真實的框架項目沒有區別(不是全部)。
“嵌入”框架是靜態框架的一個包裝,以便Xcode能獲取框架內的資源(圖片、plist、nib等)。 本次發布包括了創建靜態框架和“偽”框架的模板,以及二者的“嵌入”框架。
用哪一種模板?
本次發布有兩個模板,每個模板都有“強”“弱”兩個類別。你可以選擇最適合一種(或者兩種都安裝上)。 最大的不同是Xcode不能創建“真”框架,除非你安裝靜態框架文件xcspec在Xcode中。這真是一個遺憾(這個文件是給項目使用的,而不是框架要用的)。
簡單說,你可以這樣決定用哪一種模板:
如果你不想修改Xcode,那么請使用“偽”框架版本
如果你只是想共享二進制(不是項目),兩種都可以
如果你想把框架共享給不想修改Xcode的開發者,使用“偽”框架版本
如果你想把框架共享給修改過Xcode的開發者,使用“真”框架版本
如果你想把框架項目作為另一個項目的依賴(通過workspace或者子項目的方式),請使用“真”框架(或者“偽”框架,使用-framework——見后)
如果你想在你的框架項目中加入其他靜態庫/框架,并把它們也鏈接到最終結果以便不需要單獨添加到用戶項目中,使用“偽”框架
“偽”框架
“偽”框架是破解的“reloacatable object file”(可重定位格式的目標文件,
保存著代碼和數據,適合于和其他的目標文件連接到一起,用來創建一個可執行目標文件或者是一個可共享目標文件),它可以讓Xcode編譯出類似框架的東西——其實也是一個bundle。
“偽框架”模板把整個過程分為幾個步驟,用某些腳本去產生一個真正的靜態框架(基于靜態庫而不是reloacatable object file)。而且,框架項目還是把它定義為wrapper.cfbundle類型,一種Xcode中的“二等公民”。
因此它跟“真”靜態框架一樣可以正常工作,但當存在依賴關系時就有麻煩了。
依賴問題
如果不使用依賴,只是創建普通的項目是沒有任何問題的。但是如果使用了項目依賴(比如在workspace中),Xcode就悲劇了。當你點擊“Link
Binary With
Libraries”下方的’+’按鈕時,“偽框架”無法顯示在列表中。你可以從你的“偽”框架項目的Products下面將它手動拖入,但當你編輯你的主項目時,會出現警告:
warning: skipping file '/somewhere/MyFramework.framework'
(unexpectedfile type 'wrapper.cfbundle' in Frameworks & Libraries
build phase) 并伴隨“偽”框架中的鏈接錯誤。
幸運的是,有個辦法來解決它。你可以在”Other Linker Flags”中用”-framwork”開關手動告訴linker去使用你的框架進行鏈接:
-framework MyFramework
警告仍然存在,但起碼能正確鏈接了。
添加其他的庫/框架
如果你加入其他靜態(不是動態)庫/框架到你的“偽”框架項目中,它們將“鏈接”進你最終的二進制框架文件中。在“真”框架項目中,它們是純引用,而不是鏈接。
你可以在項目中僅僅包含頭文件而不是靜態庫/框架本身的方式避免這種情況(以便編譯通過)。
“真”框架
“真”框架各個方面都符合“真”的標準。它是真正的靜態框架,正如使用蘋果在從Xcode中去除的那個功能所創建的一樣。
為了能創建真正的靜態框架項目,你必需在Xcode中安裝一個xcspec文件。
如果你發布一個“真”框架項目(而不是編譯),希望去編譯這個框架的人必需也安裝xcspec文件(使用本次發布的安裝腳本),以便Xcode能理解目標類型。
注意:如果你正在發布完全編譯的框架,而不是框架項目,最終用戶并不需要安裝任何東西。 我已經提交一個報告給蘋果,希望他們在Xcode中更新這個文件,但那需要一點時間。
加其他靜態庫/框架
如果你加入其他靜態(不是動態)庫/框架到你的“真”框架項目,它們只會被引用,而不會象“偽”框架一樣被鏈接到最終的二進制文件中。
從早期版本升級
如果你是從Mk6或者更早的版本升級,同時使用“真”靜態框架,并且使用Xcode4.2.1以前的版本,請運行uninstall_legacy.sh以卸載早期用于Xcode的所有修正。然后再運行install.sh,重啟Xcode。如果你使用Xcode4.3以后,只需要運行install.sh并重啟Xcode。
安裝
分別運行Real Framework目錄或Fake Framework目錄下的install.sh腳本進行安裝(或者兩個你都運行)。
重啟Xcode,你將在新項目向導的Framework&Library下看到StaticiOS Framework(或者Fake Static iOS Framework)。
卸載請運行unistall.sh腳本并重啟Xcode。
創建一個iOS框架項目
創建新項目。
項目類型選擇Framework&Library下的Static iOS Framework(或者Fake Static iOS Framework)。
選擇“包含單元測試”(可選的)。
在target中加入類、資源等。
凡是其他項目要使用的頭文件,必需聲明為public。進入target的Build Phases頁,展開Copy Headers項,把需要public的頭文件從Project或Private部分拖拽到Public部分。
編譯你的 iOS 框架
選擇指定target的scheme
修改scheme的Run配置(可選)。Run配置默認使用Debug,但在準備部署的時候你可能想使用Release。
編譯框架(無論目標為iOS device和Simulator都會編譯出相同的二進制,因此選誰都無所謂了)。
從Products下選中你的framework,“show in Finder”。
在build目錄下有兩個文件夾:(yourframework).framework and (your framework).embeddedframework.
如果你的框架只有代碼,沒有資源(比如圖片、腳本、xib、coredata的momd文件等),你可以把(yourframework).framework
分發給你的用戶就行了。如果還包含有資源,你必需分發(your framework).embeddedframework給你的用戶。
為什么需要embedded framework?因為Xcode不會查找靜態框架中的資源,如果你分發(your framework).framework, 則框架中的所有資源都不會顯示,也不可用。
一個embedded framework只是一個framework之外的附加的包,包括了這個框架的所有資源的符號鏈接。這樣做的目的是讓Xcode能夠找到這些資源。
使用iOS 框架
OS框架和常規的Mac OS動態框架差不多,只是它是靜態鏈接的而已。
在你的項目中使用一個框架,只需把它拖僅你的項目中。在包含頭文件時,記住使用尖括號而不是雙引號括住框架名稱。例如,對于框架MyFramework:
import
使用問題
Headers Not Found
如果Xcode找不到框架的頭文件,你可能是忘記將它們聲明為public了。參考“創建一個iOS框架項目”第5步。
No Such Product Type
如果你沒有安裝iOS Universal Framework在Xcode,并企圖編譯一個universal框架項目(對于“真”框架,不是“假”框架),這會導致下列錯誤:
target specifies product type
'com.apple.product-type.framework.static',but there's no such product
type for the 'iphonesimulator' platform
為了編譯“真”iOS靜態框架,Xcode需要做一些改動,因此為了編譯“真”靜態框架項目,請在所有的開發環境中安裝它(對于使用框架的用戶不需要,只有要編譯框架才需要)。
The selected run destination is not valid for this action
有時,Xcode出錯并加載了錯誤的active設置。首先,請嘗試重啟Xcode。如果錯誤繼續存在,Xcode產生了一個壞的項目(因為Xcode4的一個bug,任何類型的項目都會出現這個問題)。如果是這樣,你需要創建一個新項目重來一遍。
鏈接警告
第一次編譯框架target時,Xcdoe會在鏈接階段報告找不到文件夾: ld: warning: directory not found
for
option'-L/Users/myself/Library/Developer/Xcode/DerivedData/MyFramework-ccahfoccjqiognaqraesrxdyqcne/Build/Products/Debug-iphoneos'
此時,可以clean并重新編譯target,警告會消除。
Core Data momd not found
對于框架項目和應用程序項目,Xcode會以不同的方式編譯momd(托管對象模型文件)。Xcode會簡單地在根目錄創建.mom文件,而不會創建一個.momd目錄(目錄中包含VersionInfo.plist和.mom文件)。
這意味著,當從一個embedded framework的model中實例化NSManagedObjectModel時,你必需使用.mom擴展名作為model的URL,而不是采用.momd擴展名。
NSURL *modelURL = [[NSBundle mainBundle]URLForResource:@"MyModel" withExtension:@"mom"];
Unknown class MyClass in Interface Builder file.
由于靜態框架采用靜態鏈接,linker會剔除所有它認為無用的代碼。不幸的是,linker不會檢查xib文件,因此如果類是在xib中引用,而沒有在O-C代碼中引用,linker將從最終的可執行文件中刪除類。這是linker的問題,不是框架的問題(當你編譯一個靜態庫時也會發生這個問題)。蘋果內置框架不會發生這個問題,因為他們是運行時動態加載的,存在于iOS設備固件中的動態庫是不可能被刪除的。
有兩個解決的辦法:
讓框架的最終用戶關閉linker的優化選項,通過在他們的項目的Other Linker Flags中添加-ObjC和-all_load。
在框架的另一個類中加一個該類的代碼引用。例如,假設你有個MyTextField類,被linker剔除了。假設你還有一個MyViewController,它在xib中使用了MyTextField,MyViewController并沒有被剔除。你應該這樣做:
在MyTextField中:
(void)forceLinkerLoad_ {}
在MyViewController中:
+(void) initialize { [MyTextField forceLinkerLoad_]; }
他們仍然需要添加-ObjC到linker設置,但不需要強制all_load了。
這種方法需要你多做一點工作,但卻讓最終用戶避免在使用你的框架時關閉linker優化(關閉linker優化會導致object文件膨脹)。
unexpected file type 'wrapper.cfbundle' in Frameworks &Libraries build phase
這個問題發生在把“假”框架項目作為workspace的依賴,或者把它當作子項目時(“真”框架項目沒有這個問題)。盡管這種框架項目產生了正確的靜態框架,但Xcode只能從項目文件中看出這是一個bundle,因此它在檢查依賴性時發出一個警告,并在linker階段跳過它。
你可以手動添加一個命令讓linker在鏈接階段能正確鏈接。在依賴你的靜態框架的項目的OtherLinker Flags中加入:
-framework MyFramework
警告仍然存在, 但不會導致鏈接失敗。
Libraries being linked or not being linked into the finalframework
很不幸, “真”框架和“假”框架模板在處理引入的靜態庫/框架的工作方式不同的。
“真”框架模板采用正常的靜態庫生成步驟,不會鏈接其他靜態庫/框架到最終生產物中。
“假”框架模板采用“欺騙”Xcode的手段,讓它認為是在編譯一個可重定位格式的目標文件,在鏈接階段就如同編譯一個可執行文件,把所有的靜態代碼文件鏈接到最終生成物中(盡管不會檢查是否確實目標代碼)。為了實現象“真”框架一樣的效果,你可以只包含庫/框架的頭文件到你的項目中,而不需要包含庫/框架本身。
Unrecognized selector in (some class with a category method)
如果你的靜態庫或靜態框架包含了一個模塊(只在類別代碼中聲明,沒有類實現),linker會搞不清楚,并把代碼從二進制文件中剔除。因為在最終生成的文件中沒有這個方法,所以當調用這個類別中定義的方法時,會報一個“unrecognizedselector”異常。
要解決這個,在包含這個類別的模塊代碼中加一個“假的”類。linker發現存在完整的O-C類,會將類別代碼鏈接到模塊。
我寫了一個頭文件 LoadableCategory.h,以減輕這個工作量:
import "SomeConcreteClass+MyAdditions.h"
import "LoadableCategory.h"
MAKE_CATEGORIES_LOADABLE(SomeConcreteClass_MyAdditions);
@implementation SomeConcreteClass(MyAdditions) ... @end
在使用這個框架時,仍然還需要在Build Setting的Other Linker Flags中加入-ObjC。
執行任何代碼前單元測試崩潰
如果你在Xcode4.3中創建靜態框架(或庫)target時,勾選了“withunit tests”,當你試圖運行單元測試時,它會崩潰:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x0) 0 0x00000000 --- 15 dyldbootstrap:start(...)
這是lldb中的一個bug。你可以用GDB來運行單元測試。編輯scheme,選擇Test,在Info標簽中將調試器Debugger從LLDB改為GDB。
轉載自:https://www.gitbook.com/book/leon_lizi/-framework-/details