1. 程式人生 > >iOS 靜態庫,動態庫與 Framework 淺析

iOS 靜態庫,動態庫與 Framework 淺析

靜態庫與動態庫的區別

首先來看什麼是庫,庫(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!選項控制。

參考資料

使用靜態庫的好處

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.jianshu.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!

簽名

系統在載入動態庫時,會檢查 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。

“偽框架”模板把整個過程分為