背景
項目構建
瘦身
注意事項
小結
背景
最近一直在負責公司SDK
的事宜,隨著公司業務的發展,對于有些公司內部可能有許多的項目或者對外有業務上的來往,需要將公司的某一個功能模塊或者公共組件打成Framework
或著.a
來提供給別的項目或者公司來使用,特別是在一些垂直領域如身份證識別,銀行卡掃描,視頻認證等。
項目構建
本文講解的是的是基于Cocoapods
管理的私有庫工程。
1. Target構建
這里總共建立了
4
個Target
,我們逐個進行講解。
第一個就是我們要構建的Framework
創建時需要選擇此處
修改生成的
Mach-O
格式,因為動態庫也可以是以Framework
形式存在,所以需要設置,否則默認打出來的是動態庫。將target->BuildSetting->Mach-o Type
設為Static Library
(默認為Dynamic Library
)關于底下這些參數我們可以使用默認的
依賴關系<Link Binary With Libraries
>:
1.制作Framework
可以包含.a
,也可以包含Framework
<只需將Framework的.o
目標集合文件拖進來>。
2.對于Cocoapods
管理的Framework
的Target
和Single View Application
形成的Target
是有區別的,Single View Application
形成的Cocoapods
會為我們自動依賴libPods.a
,對于Framework
需要我們手動將各個模塊的.a
添加進來。
3.關于第三方,需要和合作方確定好第三方的版本,對于合作方沒有的要協商好是對方給工程中去添加,還是自己在打SDK時一起打進去。
第二個就是我們配合Framework
使用的Bundle
創建時需要選擇此處
創建完成后需要將這里的參數修改下
Combine High Resolution Artwork 或 COMBINE_HIDPI_IMAGES
這兩項一個是OSX下的名字,一個是iOS下的名字,改為NO才可以存圖片,不然存進去是tiff。
從iOS8
開始,就可以利用Framework
將資源打入進去,這也是優于.a
的一個地方,你也可以只需要Framework
就可以,但是這里為什么還要單獨創建一個Bundle
來管理呢?
主要是因為你做出來的SDK
可能用于不同的項目,不同的項目對于膚色的要求有變化,這樣單獨拿出來一套就可以實現對于不同的項目,根據需求可以實現盲操作去替換圖片,不需要再去每個私有庫中挨個替換。
/**
第一種思路因為[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"];
當然你也可以兩種結合起來使用
這里需要注意:
1.如果你的是xib,storyboard默認是從主Bundle中去找資源,因此你需要在代碼里面重新實現下。
2.對于SDK是非常不建議使用xib,storyboard的因為維護成本太高,尤其是在彼此使用的Xcode版本不同兼容的iOS版本不同,有時是需要重新修改參數。
第三個就是我們用來檢驗Framework
,Bundle
的Demo
對于此Target
我們可以直接依賴Framework
,Bundle
來檢驗,這里我們只需要先各自Commad+B
后直接將依賴關系添加進來就可以。
你也可以在
Podfile
中讓此Target
和負責打Framework的Target
添加同樣的依賴。建議使用第二種,這樣的是直接源碼依賴,每次直接運行就可以,第一種還需要每次修改完代碼后運行前先
Clear
下,因為Framework
是有緩存的,它不參與編譯階段。
第四個就是我們用來負責打包的Aggregate
腳本。
這里首先需要說說關于架構的事情。
1、模擬器架構:2種
i386 : 32位架構 4S ~ 5
x86_64 : 64位架構 5S ~ 現在的機型
2、真機架構: 3種
armv7 : 32位架構 3GS ~ 4S
armv7s: 特殊的架構 5 ~ 5C <此架構已被Apple廢棄掉,因此我們在打SDK時可以不兼容>
amr64 : 64位架構 5S ~ 現在的機型
關于架構我們可以看官方的這幅圖,也看可以從這里查看詳情。
接下來就是打包了,其中上面第一個
Target
之所以可以使用默認的架構就是因為我們在發給合作方時要提供Release
版本的(因為當前圖中模擬器打出來Debug
中只包含當前架構),關于Release
和Debug
二者的區別這里不做說明,你可能會發現對于Release
和Debug
版本打出的Framework
大小沒有多大變化,但是二者提供給合作方之后,對方打出的ipa
大小變化是比較明顯的,我這邊相差4
到5M
的樣子,這個差值如果要讓你通過刪代碼和減小資源來彌補是一件很困難的事情。下來我們來創建一個
Aggregate
添加一個
Run Scipt
項直接可以將底下的腳本粘貼進去,此腳本會在你的工程目錄下創建一個
Products
文件夾當你構建好之后,會自動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
一份出來,注意區分Release
和Debug
模式。
2.然后在相同的模式Release
或Debug
下去運行Aggregate
。
這個是利用腳本去打,我們自己也可以手動利用命令在終端中去實現。
當你打出來后就可以看到下面的模塊
你可以使用
lipo -info
來查看你的二進制文件包含的框架其中核心就是
.o
格式的目標集合文件,我們可以使用命令來進行查看
lipo *** -thin armv7 -output ***_armv7
ar -x
首先需要從我們剛剛打出來的包中剝離出來一種架構出來(當然你也可以只Command+B
一種架構來)
查看所有的.o文件
發現這里有個
__.SYMDEF文件
利用cat命令可以查看
cat __.SYMDEF
當執行完后會在終端中輸出一大串,會發現這個是我們的類的名稱,但不包含Category
和Extension
的信息,但是你發現在.o
中是能找到拓展的,此時是否想到了為什么對于SDK
中如果有Category
時需要 Build Settings
中找到 Other Linker Flags
,并加上 -ObjC
,原因就在這里, -ObjC
相當于一個標記,告訴在鏈接階段要去鏈接整個.o
文件,并非是只鏈接__.SYMDEF
所羅列出來的。
瘦身
如果你的Framework
是從主包中脫離出來的一個模塊,或者你的Framework
已經迭代了好多個版本,難免會有許多的冗余。一般合作方對于包的大小都有要求,因此我們可以從這幾個方面去入手。
1.從資源文件下手剔除不必要的資源,如圖片,xib
,音視頻等。
這里我們可以使用LSUnusedResources,找出Framework
中沒有使用的資源將其刪掉。
2.也可以利用TinyPNG對項目要用的圖片進行壓縮。
3.可以從項目中的文件入手,利用LinkMap軟件可以清晰的看到每個類的大小,這為我們刪除類提供了依據,也可以利用上面.o
的方法來查看,利用軟件更加直觀方便。
4.可以通過設置關于打Framework
相關參數,如打Release
版本的。
注意事項
1.對于Framework
中里面建議不要使用hook
方法,一般情況下我們用的比較多的就是利用Category
去重載系統類的+(void)load
方法,然后對某個類的某些方法交換實現,因為+(void)load
方法的執行時機是在入口函數main
中去執行,它的影響是全局的,這樣的話你交換實現的代碼就會影響到合作方,或許當你Review
此段代碼時覺得里面寫的恰好給對方沒有造成什么影響,代碼很健壯而且也沒有發現在此處有Crash
現象出現過,哈哈,沒有出現可能是在你們的項目中沒有出現,但是不排除此處的代碼放到對方的項目中在某些特定的條件下就沒有Crash
,如果對方的項目是個日活超過百萬級的項目那就比較嚴重了,假如你是重載交換了UIViewController
生命周期的某個方法,想想對方的每個視圖出現都要到你這里來轉一圈,所以還是存在一定的風險的。
2.由于Objective-C
沒有命名空間,關于Framework
中的命名,一定要按照蘋果的命名規范來,否則沖突的可能性還是很大的,一般情況下對于類名大家都能做到規范,但是對于Catergory
、Extention
或者 extern
等,就時常不太嚴謹,此時如果恰好方法名重復,就會造成方法實現替代的沖突,對于這種情況是發生在運行時的,也就是說如果測試沒有覆蓋到則可能將此問題附帶上線。
我們可以在工程中這樣進行搜索Catergory
。
小結
1.盡量不要用xib,storyboard
不同版本Xcode
打包維護成本較高。
2.打包時Xcode
版本盡量小于等于合作方的版本,可以避免一些宏找不到的問題。
3.同一份代碼使用不同的Xcode
版本打出來的大小是不一樣的。
4.最終上線時要使用Release
版的。
5.命名嚴格的按照Apple
的命名規范來。