技 術 文 章 / 超 人
關于庫思維導圖:
庫
概念
什么是庫
庫是共享程序代碼的方式拢蛋。庫從本質(zhì)上來說是一種可執(zhí)行代碼的二進制格式,可以被載入內(nèi)存中執(zhí)行荡灾。在開發(fā)過程中瓤狐,一些核心技術或者常用框架,出于安全性和穩(wěn)定性的考慮批幌,不想被外界知道础锐,所以會把核心代碼打包成庫,只暴露出頭文件以供使用荧缘。庫分靜態(tài)庫和動態(tài)庫兩種皆警。
庫的分類
- 靜態(tài)庫
對于靜態(tài)庫而言,類似于一個編譯好的.o
的集合截粗。在build的過程中信姓,只會參與鏈接的操作,鏈接器會將靜態(tài)庫中被使用的部分合并到可執(zhí)行文件中去绸罗,用函數(shù)的實際地址來代替函數(shù)引用意推。
存在 .a
和 .framework
兩種形式。 .a
要有.h
文件以及資源文件配合
1.
.a
是一個純二進制文件
2.
.framework
中除了有二進制文件之外還有資源文件珊蟀。 .framework
文件可以直接使用菊值。
總的來說
,.a
+ .h
+ sourceFile
= .framework
。所以創(chuàng)建靜態(tài)庫最好還是用.framework
的形式腻窒。
鏈接流程如下圖:
- 動態(tài)庫
存在.framework和.tbd兩種形式昵宇。
在 iOS8
之前,蘋果不允許第三方框架使用動態(tài)方式加載儿子,從 iOS8
開始允許開發(fā)者有條件地創(chuàng)建和使用動態(tài)框架
瓦哎,這種框架叫做 Cocoa Touch Framework
。雖然同樣是動態(tài)框架柔逼,但是和系統(tǒng) framework
不同蒋譬,app 中的使用的 Cocoa Touch Framework
在打包和提交 app 時會被放到 app bundle 中,運行在沙盒里愉适,而不是系統(tǒng)中羡铲。也就是說,不同的 app 就算使用了同樣的 framework
儡毕,但還是會有多份的框架被分別簽名,打包和加載扑媚。不過iOS8
上開放了App Extension
功能腰湾,可以為一個應用創(chuàng)建插件,這樣主app和插件之間共享動態(tài)庫還是可行的疆股。
動態(tài)鏈接是使用了 Procedure Linkage Table (PLT)
费坊。首先這個 PLT
列出了程序中每一個函數(shù)的調(diào)用,當程序開始運行旬痹,如果動態(tài)庫被加載到內(nèi)存中附井,PLT
會去尋找動態(tài)的地址并記錄下來,如果函數(shù)被調(diào)用過的話两残,下一次調(diào)用就可以通過 PLT
直接跳轉(zhuǎn)了永毅。
- 優(yōu)劣
1.
靜態(tài)庫,在鏈接時會被完整地復制到可執(zhí)行文件中人弓,被多次使用就有多份冗余拷貝
沼死,靜態(tài)庫還有一個較重要的缺點,多個靜態(tài)庫之間文件名和靜態(tài)變量名字不能重復崔赌,否則編譯會報錯或者找錯對象意蛀。好處很明顯,編譯完成之后健芭,庫文件實際上就沒有作用了县钥。目標程序沒有外部依賴,直接就可以運行慈迈。當然其缺點也很明顯若贮,就是會使用目標程序的體積增大
。
2.
動態(tài)庫,與靜態(tài)庫相反兜看,動態(tài)庫在編譯時并不會被拷貝到目標程序中锥咸,目標程序中只會存儲指向動態(tài)庫的引用。等到程序運行時细移,動態(tài)庫才會被真正加載進來搏予。系統(tǒng)的動態(tài)庫不需要拷貝到目標程序中,自建的動態(tài)庫可以由工程內(nèi)的多個庫共享弧轧,因此可以減小目標程序的體積雪侥。但是,由于其把靜態(tài)鏈接做的事情都搬到運行時來做精绎,程序的啟動會變慢速缨。
庫的創(chuàng)建
.a靜態(tài)庫的創(chuàng)建
創(chuàng)建一個 .a
靜態(tài)庫項目,如下圖所示:
靜態(tài)庫的文件列表如下代乃,在 products 文件夾內(nèi)的就是要生成的靜態(tài)庫旬牲。此刻是紅色的,等到靜態(tài)庫編譯成功就會變成黑色搁吓。
現(xiàn)在新建自己的類PrintString.h
原茅,聲明和實現(xiàn)一個第三方庫的方法。
現(xiàn)在可以打包這個靜態(tài)庫了堕仔。由于模擬器和真機架構(gòu)不同擂橘,需要選擇該包將運行在哪個環(huán)境下,如下圖所示摩骨,選擇運行在真機上:
打包生成了靜態(tài)庫在 products 文件夾內(nèi):
打開 products 文件夾通贞, 但是此時暴露出來的頭文件并沒有PrintString.h
。需要對暴露的頭文件進行設置恼五。
如下圖昌罩,在 Build Phase ,的 Copy Files 目錄下加入想要公開的頭文件:
現(xiàn)在再 run 一次唤冈,就得到了正確的靜態(tài)庫峡迷。
現(xiàn)在,可以測試一下這個靜態(tài)庫你虹』娓悖可以再創(chuàng)建一個工程,把庫拖進去傅物。不過更推薦如下圖所示夯辖,新建一個 target :
先要在工程和庫間建立關聯(lián)。如下圖所示董饰,在 Link Binary With Libraries
中添加庫:
在 ViewController.m
中調(diào)用庫的方法:
現(xiàn)在可以運行了蒿褂,不過運行前要選擇對 target :
可以在控制臺看到庫中的方法被調(diào)用了:
.framework的創(chuàng)建
動態(tài)framework
創(chuàng)建一個framework:
創(chuàng)建后的文件列表如下圆米,可以看到只有一個 framework.h
頭文件。通過注釋啄栓,我們可以理解娄帖,這個頭文件是所有 public 頭文件的集合:
將前面創(chuàng)建的 PrintString.h 和 PrintString.m導入,并且 import 到 framework.h 中去:
設置需要暴露的頭文件昙楚,頭文件默認在 project header 中近速,將需要暴露出來的拖到 public header 中去。
我們可以看到此處有三種頭文件堪旧,分別是 project header 削葱, public header , private header 淳梦。區(qū)別如下:
Public: The interface is finalized and meant to be used by your product’s clients. A public header is included in the product as readable source code without restriction.
Private: The interface isn’t intended for your clients or it’s in early stages of development. A private header is included in the product, but it’s marked “private”. Thus the symbols are visible to all clients, but clients should understand that they're not supposed to use them.
Project: The interface is for use only by implementation files in the current project. A project header is not included in the target, except in object code. The symbols are not visible to clients at all, only to you.
生成的 framework 文件目錄如下:
將生成的 framework 放入工程中測試析砸,編譯通過,運行時出現(xiàn)如下錯誤:
需要將動態(tài)庫嵌入工程的 bundle 中爆袍。因此首繁,需要在 General 中的 Embedded Binary 一項中加入相應動態(tài)庫:
現(xiàn)在就可以正確運行了
動態(tài)framework
靜態(tài)framework和動態(tài)framework創(chuàng)建的基本流程一致,唯一的區(qū)別需要設置 Mach-O Type 為 Static Library
:
靜態(tài)庫與動態(tài)庫的引用
一個庫的開發(fā)經(jīng)常會需要用到其他的庫(如 AFNetWorking
)的配合陨囊,因此蛮瞄,需要在庫中嵌入其他的庫。如何在自己的靜態(tài)/動態(tài)庫中集成第三方的靜態(tài)/動態(tài)庫是我比較困惑的點谆扎。
網(wǎng)上沒有找到相關教程,以下是我不斷嘗試后得出的結(jié)論芹助,如果有錯誤還請指正堂湖。
動態(tài)庫引用靜態(tài)庫
創(chuàng)建一個動態(tài)庫 DynamicWithStatic
。
使用 cocoapods 的方式為動態(tài)庫引入靜態(tài)庫状土。在工程目錄下新建 podfile
,寫入:
target 'DynamicWithStatic' do
pod 'SVProgressHUD'
end
這里引入SVProgressHUD
无蜂,因為調(diào)試起來比較簡單。
在執(zhí)行完 pod install
后蒙谓,打開 Frameworks.xcworkspace
,在其中添加SVProgress
類〕饧荆現(xiàn)在的工程目錄如下:
在SVProgress.h
中添加代碼:
typedef void(^SVProgresshud)();
#import <Foundation/Foundation.h>
@interface SVProgress : NSObject
+ (SVProgresshud)getBlock;
@end
在SVProgress.m
中添加代碼:
#import "SVProgress.h"
#import "SVProgressHUD.h"
@implementation SVProgress
+(SVProgresshud)getBlock{
return ^(){
[SVProgressHUD showSuccessWithStatus:@"成功!"];
};
}@end
SVProgress
類的目的是提供一個 block 以供調(diào)用累驮。將 SVProgress.h
頭文件暴露出來后酣倾, run 生成動態(tài)庫。
動態(tài)庫內(nèi)引用靜態(tài)庫相當于直接把代碼寫入動態(tài)庫中谤专,非常的簡單≡晡現(xiàn)在思考一個問題,如果工程中引用了這個動態(tài)庫置侍,并且工程本身也用到了 SVProgressHUD
庫映之,那么會發(fā)生什么拦焚?嘗試一下,在 Pod 中為 FrameworkTest
添加 SVProgressHUD
,會產(chǎn)生如下的警告:
SVProgressHUD
庫中的各個類在工程和庫中都進行了實現(xiàn)杠输,但是沒有指明使用哪一個實現(xiàn)赎败。不過這個警告,并不影響運行蠢甲,非強迫癥可以選擇無視僵刮,那么對于強迫癥患者,如何消除掉這些警告呢峡钓?修改庫中 SVProgressHUD
中各個類的命名妓笙。有兩種方式:手動和自動,將在下面介紹能岩。
動態(tài)庫引用動態(tài)庫
這次被引用的動態(tài)庫還是 SVProgressHUD
庫寞宫。先用 pod
下載 SVProgressHUD
的源碼,然后打包成動態(tài)庫拉鹃,這里就不多做說明了辈赋。將動態(tài)庫 SVProgressHUD
拖入工程中,新建 DynamicWithDynamic
動態(tài)庫膏燕。將上面的 SVProgress.h
和 SVProgress.m
拖入 DynamicWithDynamic
中钥屈,設置暴露出 SVProgress.h
。
記得注意一定要在 DynamicWithDynamic
中引入 SVProgressHUD
,如下圖所示:
現(xiàn)在 run 一下坝辫,就可以成功生成了篷就。下面在 FrameworkTest
中測試,使其 embed DynamicWithDynamic近忙。微調(diào)下 ViewController.m
使其引入目標庫竭业,運行。在運行中出現(xiàn)如下錯誤:
這個錯誤上面也提到過了及舍,是因為必要的動態(tài)庫沒有 embed 進工程中未辆。但是明明已經(jīng)將 DynamicWithDynamic
embed了啊。好吧锯玛,由于 SVProgressHUD
現(xiàn)在是動態(tài)庫了咐柜,還需要將 SVProgressHUD
庫 embed 進 FrameworkTest
中。
動態(tài)庫引入動態(tài)庫攘残,不會將要引入的動態(tài)庫打包到自身中拙友,即只是 link 產(chǎn)生關聯(lián),而不是 embed 嵌入歼郭。 需要在外部使用該動態(tài)庫時献宫,手動 embed 動態(tài)庫內(nèi)要使用的動態(tài)庫。這樣的做法很麻煩实撒,那么有什么意義呢姊途?正如動態(tài)庫本身的作用涉瘾,如果工程本身也要用到該 ·SVProgressHUD·
動態(tài)庫時,那么僅需導入一份捷兰,就不會產(chǎn)生重復代碼了立叛。
靜態(tài)庫引用靜態(tài)庫
新建一個 target
命名為 StaticWithStatic
,使用 cocoapods
管理 SVProgressHUD
贡茅∶厣撸基本方法和上面一樣,唯一需要改變的地方是 Mach-O
需要改為 Static Library
顶考,運行赁还。
然而運行失敗,報錯如下:
大概意思是在對 ViewController.h
進行鏈接的時候沒有找到 SVProgressHUD.m
驹沿。這就很奇怪了艘策,明明已經(jīng)在 StaticWithStatic
中通過 cocoapods
管理了,怎么會沒有 SVProgressHUD.m
呢渊季?我想可能是因為cocoapods
只是起到 Link 作用朋蔫,并沒有把第三方庫也打進去,那么為什么動態(tài)庫引入靜態(tài)庫的時候是可行的呢却汉?這就不太清楚了驯妄。雖然不知道原因,但是解決方法是有的合砂。需要在工程里青扔,即 FrameworkTest
的 cocoapods
中添加 SVProgressHUD
:
target 'DynamicWithStatic' do
pod 'SVProgressHUD'
end
target 'StaticWithStatic' do
pod 'SVProgressHUD'
end
target 'FrameworksTest' do
pod 'SVProgressHUD'
end
target 'DynamicWithDynamic' do
end
再次 build ,錯誤消除翩伪,可以使用赎懦。
靜態(tài)庫引用動態(tài)庫
靜態(tài)庫貌似不能引用動態(tài)庫。如果嘗試一下幻工,會發(fā)現(xiàn),即使將動態(tài)庫 Link Binary With Libraries
入靜態(tài)庫黎茎,也是找不到 SVProgressHUD.h
頭文件的囊颅。一些需要知道的點debug
與release
- 庫分為
debug
和release
兩種版本。一般來說, 我們應該發(fā)布的是release
版本傅瞻。 -
debug
:調(diào)試版本, 系統(tǒng)本身也會有一些調(diào)試代碼. 此版本體積會稍大, 運行會稍慢踢代。 -
release
: 發(fā)布版本, 系統(tǒng)會去除調(diào)試代碼, 體積變小, 運行速度變快. 對用戶來說沒有明顯的感覺。
debug
與 release
的設置方式如下圖:
對于別人給的庫嗅骄,貌似并不能區(qū)分是 debug
還是 release
版本的胳挎。
多架構(gòu)編譯
庫不僅按 debug
和 release
分類,還會因為運行系統(tǒng)的不同而編譯出不同框架的版本溺森。上面的例子都是在真機下的編譯慕爬,為 arm64
版本窑眯,在其他的框架下不能正確運行。
框架分類:
模擬器架構(gòu):
-
i386
: 32位架構(gòu) 4S ~ 5 -
x86_64
: 64位架構(gòu) 5S ~ 現(xiàn)在的機型
真機架構(gòu):
-
arm7
: 在最老的支持iOS7
的設備上使用 -
arm7s
: 在iPhone5和5C上使用 -
arm64
: 運行于iPhone5S的64位 ARM 處理器 上
修改框架的方式如下圖:
debug
項默認為 YES 磅甩,表示僅生成當前選擇的框架的庫; release
項默認為 NO 姥卢,表示生成支持所有模擬器或真機的庫卷要。生成的庫將會保存在 products
目錄下的不同分類目錄內(nèi):
lipo
lipo 是個很有用的命令,主要用來查看庫支持的架構(gòu)以及合并拆分庫独榴。
-info
查看剛才編譯的 Framework 庫在 debug 和 release 下支持的框架:
[圖片上傳中僧叉。栋荸。恩袱。(33)]查看庫
可以看到正如上面所說 debug
下不是 fat file ,只支持 arm64
呼巷, release
下是 fat file 掷豺, 支持 arm7
和 arm64
捞烟。
-create
上面生成的庫,要么是只支持模擬器的当船,要么是只支持真機的题画,那么如何才能又能兼顧真機和模擬器呢? -create
使用方式:
lipo -create 庫1 庫2 -output 新庫
使用結(jié)果如下圖:
-thin
如果有一個 fat file 但是你不需要支持那么多框架德频,也可以通過拆分苍息,為庫瘦身, -thin 使用方式:
lipo 舊庫 -thin 需拆分框架 -output 新庫
使用結(jié)果如下圖:
bundle
在 framework
中使用 storyboard
/xib
創(chuàng)建的頁面壹置,可以直接訪問 framework
中圖片資源竞思。但是 framework
中通過imageNamed:
方式加載的照片都會丟失。這是因為 imageNamed:
的方法默認是從 mainBundle
中查找資源的钞护,而 framework
中的照片是從 framework
內(nèi)部加載的盖喷,這是的 bundle
并不是 mainBundle
,而是存在于主程序的 docment
文件中的 framework
包难咕,圖片加載的路徑發(fā)生了變化课梳,自然找不到圖片資源,所以需要修改加載圖片的方法余佃!
一般的方法是創(chuàng)建一個 bundle :
bundle 一般和庫命名相同暮刃。需要注意的是, bundle 并不會被打包進庫中的爆土,而是添加要單獨添加到工程中椭懊,和 framework 相獨立的兩部分。向 bundle 中直接添加圖片:
先在主工程的 Copy Bundle Resources 中添加 bundle 步势,如下圖所示:
現(xiàn)在就可以通過[NSBundle mainBundle]
獲取圖片氧猬,一下兩種方式皆可:
[UIImage imageNamed:[[[NSBundle mainBundle] pathForResource:@"Frameworks" ofType:@"bundle"] stringByAppendingString:@"/Images/author.png"]];
[UIImage imageNamed:@"Frameworks.bundle/Images/author.png"];
修改bundle Setting
- 修改Architectures中的Base SDK為latest IOS
- 修改
Build Options
的Enable Bitcode
為NO
- 修改
signing
的Code signing identity
為Don't Code Sign
(在提交蘋果審核時背犯,bundle如果有任何簽名都會被拒)
- 修改
User-Defined
的COMBINE_HIDPI_IMAGES為NO
(iOS
創(chuàng)建Bundle
時放入的圖片資源(.png)在默認配置下會被轉(zhuǎn)為.tiff格式,使用的時候找不到狂窑。因為在iOS中創(chuàng)建bundle
時會用一個“hack
”媳板,為了使所有的運行需要更改一個配置。)
-在靜態(tài)庫中的Build Phases
的Target Dependencies
添加Bundle
泉哈。這樣以后編譯靜態(tài)庫時蛉幸,bundle
會自動編譯。無需單獨再編譯
Target Dependencies Target
依賴,某些Target可能依賴某個Target輸出的值,這里設置依賴
Copy Bundle Resources
是指生成的product的.app內(nèi)將包含哪些資源文件
Compile Sources
是指將有哪些源代碼被編譯
Link Binary With Libraries
是指編譯過程中會引用哪些庫文件
cocoapods打包庫
好了丛晦,終于到最后一部分了奕纫。前面已經(jīng)介紹了手動創(chuàng)建庫的方式,那么如何自動創(chuàng)建一個庫烫沙?另外一點匹层,前面提到過,在動態(tài)庫內(nèi)引入靜態(tài)庫锌蓄,會和項目本身由 cocoapods 引入的庫同名沖突升筏,如何消除這一沖突?以上問題都可以通過 cocoapods 打包庫實現(xiàn)
創(chuàng)建工程
只需要輸入 pod 的 lib
命令即可完成初始項目的搭建:
pod lib create StaticWithCocoapods
輸出指令后瘸爽,會提示確認五個問題您访,按需求回答即可:
稍等片刻,就會自動生成一個工程剪决。
配置信息
在項目目錄下有一個 xxx.podspec
配置文件灵汪,需要進行修改,摘錄如下:
Pod::Spec.new do |s|
s.name = 'StaticWithCocoapods'
s.version = '0.1.0'
s.summary = 'A short description of StaticWithCocoapods.'
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = <<-DESCTODO: Add long description of the pod here. DESC
s.homepage = 'https://github.com/zhang759740844'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Zachary' => '759740844@qq.com' }
s.source = { :git => '/Users/zachary/Desktop/StaticWithCocoapods', :tag => s.version.to_s }
s.ios.deployment_target = '8.0'
s.source_files = 'StaticWithCocoapods/Classes/**/*'
s.resource_bundles = { 'StaticWithCocoapods' => ['StaticWithCocoapods/Assets/*.png'] }
s.public_header_files = 'StaticWithCocoapods/Classes/**/*.h'
s.frameworks = 'UIKit', 'MapKit' s.dependency 'AFNetworking', '~> 2.3'
s.denpendency 'SVProgressHUD'end
s.version 表示的是當前類庫的版本號
s.source 表示當前類庫源
s.sources_files 表示類庫的源文件存放目錄
s.resource_bundles 表示資源文件存放目錄
s.frameworks 表示類庫依賴的framework
s.dependency 表示依賴的第三方類庫
其中要說明的是:
- source 可以填寫遠端 git 倉庫柑潦,也可以是像我寫的那樣的本地 git 倉庫享言。
- 依賴項不僅要包含你自己類庫的依賴,還要包括所有第三方類庫的依賴渗鬼,只有這樣當你的類庫打包成 .a 或 .framework 時才能讓其他項目正常使用览露。
- source_file 路徑中出現(xiàn)的通配符 *表示匹配任意字符, **表示匹配所有當前文件夾和子文件夾譬胎。
- source_bundles 中花括號內(nèi)的 'StaticWithCocoapods'
就表示一個 StaticWithCocoapods
bundle差牛。
添加文件
向 sources_files
和 public_header_files
以及 resource_bundle
中添加圖片和類文件。在 demo
的文件夾下執(zhí)行 pod install
∫瘢現(xiàn)在打開 demo
工程,可以看到創(chuàng)建的 StaticWithCocoapods
庫的文件結(jié)構(gòu)如下圖:
到這里一切正常累舷,也可以使用 SVProgressHUD
浩考,但是當我想用 [UIImage imageNamed:[[[NSBundle mainBundle] pathForResource:@"StaticWithCocoapods" ofType:@"bundle"] stringByAppendingString:@"/author.png"]];
加載圖片資源文件時,一直返回 nil
被盈。
好吧析孽。雖然網(wǎng)上cocoapods打包教程不少搭伤,但是真正試過添加圖片的人應該不多,最后在 Stackoverflow 的一個評論里總算找到了解決方法[Cocoapods]:Resource Bundle not accessbile
原先 demo 中 profile 的內(nèi)容如下:
use_frameworks
!target 'StaticWithCocoapods_Example' do
pod 'StaticW', :path => '../'
target 'StaticWithCocoapods_Tests' do
inherit! :search_paths
pod 'FBSnapshotTestCase'
end
end
現(xiàn)在要刪除 use_frameworks!
以及其相關內(nèi)容袜瞬,變成這樣:
target 'StaticWithCocoapods_Example' do
pod 'StaticWithCocoapods', :path => ‘../‘
end
再次嘗試加載圖片怜俐,可以得到正確結(jié)果. _
提交代碼
- 使用 sourcetree 添加本地倉庫
- 提交上面的所有改動
- 為改動添加 tag 為 0.1.0
設置好后如圖:
這里要注意,tag 一定要打上邓尤,版本控制的時候就是以 tag 來辨別的拍鲤。
驗證類庫
開發(fā)完成靜態(tài)類庫之后,需要運行pod lib lint驗證一下類庫是否符合pod的要求汞扎。添加 --allow-warnings
忽略警告:
打包庫
打包庫需要一個 cocoapods 的插件 cocoapods-packager來完成類庫的打包季稳。
在終端執(zhí)行以下命令,安裝插件:
sudo gem install cocoapods-packager
在目標文件夾內(nèi)執(zhí)行以下命令澈魄,完成打包:
pod package StaticWithCocoapods.podspec --force
打包成 .framework
景鼠,也可以用--library
打包成 .a
。不過 cocoapods
之前有過打包成 .a
但是沒有暴露出頭文件的bug
痹扇,懶得試有沒有修復了铛漓,所以還是都打成 .framework
吧。
現(xiàn)在在目標文件夾下就會多出一個 StaticWithCocoapods-0.2.0
目錄鲫构,里面是打包好的 framework
浓恶。至于如何將打包好的庫添加到 cocoapods
的官方庫內(nèi),這個就沒有研究了芬迄,因為问顷,在很長的一段時間內(nèi)我都不會用到。有興趣的可以看看 cocoapods
的官方文檔禀梳,自行研究下杜窄。