背景
項目構(gòu)建
瘦身
注意事項
小結(jié)
背景
最近一直在負責公司SDK
的事宜逛薇,隨著公司業(yè)務的發(fā)展刹泄,對于有些公司內(nèi)部可能有許多的項目或者對外有業(yè)務上的來往外里,需要將公司的某一個功能模塊或者公共組件打成Framework
或著.a
來提供給別的項目或者公司來使用,特別是在一些垂直領(lǐng)域如身份證識別特石,銀行卡掃描盅蝗,視頻認證等。
項目構(gòu)建
本文講解的是的是基于Cocoapods
管理的私有庫工程姆蘸。
1. Target構(gòu)建
這里總共建立了
4
個Target
墩莫,我們逐個進行講解芙委。
第一個就是我們要構(gòu)建的Framework
創(chuàng)建時需要選擇此處
修改生成的
Mach-O
格式,因為動態(tài)庫也可以是以Framework
形式存在狂秦,所以需要設(shè)置灌侣,否則默認打出來的是動態(tài)庫。將target->BuildSetting->Mach-o Type
設(shè)為Static Library
(默認為Dynamic Library
)關(guān)于底下這些參數(shù)我們可以使用默認的
依賴關(guān)系<Link Binary With Libraries
>:
1.制作Framework
可以包含.a
裂问,也可以包含Framework
<只需將Framework的.o
目標集合文件拖進來>侧啼。
2.對于Cocoapods
管理的Framework
的Target
和Single View Application
形成的Target
是有區(qū)別的,Single View Application
形成的Cocoapods
會為我們自動依賴libPods.a
,對于Framework
需要我們手動將各個模塊的.a
添加進來堪簿。
3.關(guān)于第三方痊乾,需要和合作方確定好第三方的版本,對于合作方?jīng)]有的要協(xié)商好是對方給工程中去添加戴甩,還是自己在打SDK時一起打進去符喝。
第二個就是我們配合Framework
使用的Bundle
創(chuàng)建時需要選擇此處
創(chuàng)建完成后需要將這里的參數(shù)修改下
Combine High Resolution Artwork 或 COMBINE_HIDPI_IMAGES
這兩項一個是OSX下的名字,一個是iOS下的名字,改為NO才可以存圖片,不然存進去是tiff。
從iOS8
開始甜孤,就可以利用Framework
將資源打入進去,這也是優(yōu)于.a
的一個地方畏腕,你也可以只需要Framework
就可以缴川,但是這里為什么還要單獨創(chuàng)建一個Bundle
來管理呢?
主要是因為你做出來的SDK
可能用于不同的項目描馅,不同的項目對于膚色的要求有變化把夸,這樣單獨拿出來一套就可以實現(xiàn)對于不同的項目,根據(jù)需求可以實現(xiàn)盲操作去替換圖片,不需要再去每個私有庫中挨個替換铭污。
/**
第一種思路因為[NSBundle mainBundle]拿到的是我們應用的主Bundle恋日,而我們的***.Bundle是其中一部分,因此我們可以先從主Bundle中將我們的
***.Bundle拿出來嘹狞,然后取資源時將所用的Bundle寫成***.Bundle即可岂膳。
*/
//返回的是***.Bundle
#define RESOURCE_BUNDLE [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"***" ofType:@"bundle"]]
//返回的是UIImage
#define IMAGE(imageName) [UIImage imageNamed:imageName inBundle:RESOURCE_BUNDLE compatibleWithTraitCollection:nil]
//返回的是資源文件路徑NSString
#define FILEPATH_STRING(fileName,type) [RESOURCE_BUNDLE pathForResource:fileName ofType:type]
/**
第二種思路可以將Bundle看作一個文件夾在原來我們訪問資源的方式上,多加一條路徑即可磅网。
*/
UIImage *image = [UIImage imageNamed:@"***.bundle/loadingicon"];
NSString *path = [[NSBundle mainBundle] pathForResource:@"***.bundle/Info" ofType:@"plist"];
當然你也可以兩種結(jié)合起來使用
這里需要注意:
1.如果你的是xib,storyboard默認是從主Bundle中去找資源谈截,因此你需要在代碼里面重新實現(xiàn)下。
2.對于SDK是非常不建議使用xib,storyboard的因為維護成本太高涧偷,尤其是在彼此使用的Xcode版本不同兼容的iOS版本不同簸喂,有時是需要重新修改參數(shù)。
第三個就是我們用來檢驗Framework
燎潮,Bundle
的Demo
對于此Target
我們可以直接依賴Framework
,Bundle
來檢驗喻鳄,這里我們只需要先各自Commad+B
后直接將依賴關(guān)系添加進來就可以。
你也可以在
Podfile
中讓此Target
和負責打Framework的Target
添加同樣的依賴确封。建議使用第二種除呵,這樣的是直接源碼依賴再菊,每次直接運行就可以,第一種還需要每次修改完代碼后運行前先
Clear
下竿奏,因為Framework
是有緩存的袄简,它不參與編譯階段。
第四個就是我們用來負責打包的Aggregate
腳本泛啸。
這里首先需要說說關(guān)于架構(gòu)的事情绿语。
1、模擬器架構(gòu):2種
i386 : 32位架構(gòu) 4S ~ 5
x86_64 : 64位架構(gòu) 5S ~ 現(xiàn)在的機型
2候址、真機架構(gòu): 3種
armv7 : 32位架構(gòu) 3GS ~ 4S
armv7s: 特殊的架構(gòu) 5 ~ 5C <此架構(gòu)已被Apple廢棄掉吕粹,因此我們在打SDK時可以不兼容>
amr64 : 64位架構(gòu) 5S ~ 現(xiàn)在的機型
關(guān)于架構(gòu)我們可以看官方的這幅圖,也看可以從這里查看詳情岗仑。
接下來就是打包了匹耕,其中上面第一個
Target
之所以可以使用默認的架構(gòu)就是因為我們在發(fā)給合作方時要提供Release
版本的(因為當前圖中模擬器打出來Debug
中只包含當前架構(gòu)),關(guān)于Release
和Debug
二者的區(qū)別這里不做說明荠雕,你可能會發(fā)現(xiàn)對于Release
和Debug
版本打出的Framework
大小沒有多大變化稳其,但是二者提供給合作方之后,對方打出的ipa
大小變化是比較明顯的,我這邊相差4
到5M
的樣子炸卑,這個差值如果要讓你通過刪代碼和減小資源來彌補是一件很困難的事情既鞠。下來我們來創(chuàng)建一個
Aggregate
添加一個
Run Scipt
項直接可以將底下的腳本粘貼進去,此腳本會在你的工程目錄下創(chuàng)建一個
Products
文件夾當你構(gòu)建好之后,會自動Open
盖文。
if [ "${ACTION}" = "build" ]
then
INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.framework
DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework
SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"
open "${SRCROOT}/Products"
fi
打包的流程:
1.先各在模擬器和Generic iOS Device
下Command+B
一份出來嘱蛋,注意區(qū)分Release
和Debug
模式。
2.然后在相同的模式Release
或Debug
下去運行Aggregate
五续。
這個是利用腳本去打洒敏,我們自己也可以手動利用命令在終端中去實現(xiàn)。
當你打出來后就可以看到下面的模塊
你可以使用
lipo -info
來查看你的二進制文件包含的框架其中核心就是
.o
格式的目標集合文件疙驾,我們可以使用命令來進行查看
lipo *** -thin armv7 -output ***_armv7
ar -x
首先需要從我們剛剛打出來的包中剝離出來一種架構(gòu)出來(當然你也可以只Command+B
一種架構(gòu)來)
查看所有的.o文件
發(fā)現(xiàn)這里有個
__.SYMDEF文件
利用cat命令可以查看
cat __.SYMDEF
當執(zhí)行完后會在終端中輸出一大串凶伙,會發(fā)現(xiàn)這個是我們的類的名稱,但不包含Category
和Extension
的信息荆萤,但是你發(fā)現(xiàn)在.o
中是能找到拓展的镊靴,此時是否想到了為什么對于SDK
中如果有Category
時需要 Build Settings
中找到 Other Linker Flags
,并加上 -ObjC
链韭,原因就在這里偏竟, -ObjC
相當于一個標記,告訴在鏈接階段要去鏈接整個.o
文件敞峭,并非是只鏈接__.SYMDEF
所羅列出來的踊谋。
瘦身
如果你的Framework
是從主包中脫離出來的一個模塊,或者你的Framework
已經(jīng)迭代了好多個版本旋讹,難免會有許多的冗余殖蚕。一般合作方對于包的大小都有要求轿衔,因此我們可以從這幾個方面去入手。
1.從資源文件下手剔除不必要的資源睦疫,如圖片害驹,xib
,音視頻等蛤育。
這里我們可以使用LSUnusedResources,找出Framework
中沒有使用的資源將其刪掉宛官。
2.也可以利用TinyPNG對項目要用的圖片進行壓縮。
3.可以從項目中的文件入手瓦糕,利用LinkMap軟件可以清晰的看到每個類的大小底洗,這為我們刪除類提供了依據(jù),也可以利用上面.o
的方法來查看咕娄,利用軟件更加直觀方便亥揖。
4.可以通過設(shè)置關(guān)于打Framework
相關(guān)參數(shù),如打Release
版本的圣勒。
注意事項
1.對于Framework
中里面建議不要使用hook
方法费变,一般情況下我們用的比較多的就是利用Category
去重載系統(tǒng)類的+(void)load
方法,然后對某個類的某些方法交換實現(xiàn),因為+(void)load
方法的執(zhí)行時機是在入口函數(shù)main
中去執(zhí)行圣贸,它的影響是全局的胡控,這樣的話你交換實現(xiàn)的代碼就會影響到合作方,或許當你Review
此段代碼時覺得里面寫的恰好給對方?jīng)]有造成什么影響旁趟,代碼很健壯而且也沒有發(fā)現(xiàn)在此處有Crash
現(xiàn)象出現(xiàn)過,哈哈庇绽,沒有出現(xiàn)可能是在你們的項目中沒有出現(xiàn)锡搜,但是不排除此處的代碼放到對方的項目中在某些特定的條件下就沒有Crash
,如果對方的項目是個日活超過百萬級的項目那就比較嚴重了瞧掺,假如你是重載交換了UIViewController
生命周期的某個方法耕餐,想想對方的每個視圖出現(xiàn)都要到你這里來轉(zhuǎn)一圈,所以還是存在一定的風險的辟狈。
2.由于Objective-C
沒有命名空間,關(guān)于Framework
中的命名肠缔,一定要按照蘋果的命名規(guī)范來,否則沖突的可能性還是很大的哼转,一般情況下對于類名大家都能做到規(guī)范明未,但是對于Catergory
、Extention
或者 extern
等壹蔓,就時常不太嚴謹,此時如果恰好方法名重復趟妥,就會造成方法實現(xiàn)替代的沖突,對于這種情況是發(fā)生在運行時的佣蓉,也就是說如果測試沒有覆蓋到則可能將此問題附帶上線披摄。
我們可以在工程中這樣進行搜索Catergory
亲雪。
小結(jié)
1.盡量不要用xib,storyboard
不同版本Xcode
打包維護成本較高。
2.打包時Xcode
版本盡量小于等于合作方的版本,可以避免一些宏找不到的問題疚膊。
3.同一份代碼使用不同的Xcode
版本打出來的大小是不一樣的义辕。
4.最終上線時要使用Release
版的。
5.命名嚴格的按照Apple
的命名規(guī)范來寓盗。