背景
項目構(gòu)建
瘦身
注意事項
小結(jié)
背景
最近一直在負責公司SDK的事宜,隨著公司業(yè)務(wù)的發(fā)展,對于有些公司內(nèi)部可能有許多的項目或者對外有業(yè)務(wù)上的來往,需要將公司的某一個功能模塊或者公共組件打成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]拿到的是我們應(yīng)用的主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ī)范來。