關于iOS-SDK那些事

  • 背景
  • 項目構建
  • 瘦身
  • 注意事項
  • 小結

背景

最近一直在負責公司SDK的事宜,隨著公司業務的發展,對于有些公司內部可能有許多的項目或者對外有業務上的來往,需要將公司的某一個功能模塊或者公共組件打成Framework或著.a來提供給別的項目或者公司來使用,特別是在一些垂直領域如身份證識別,銀行卡掃描,視頻認證等。

項目構建

本文講解的是的是基于Cocoapods管理的私有庫工程。

工程目錄

1. Target構建

Target目錄

這里總共建立了4Target,我們逐個進行講解。

第一個就是我們要構建的Framework

創建時需要選擇此處

選擇Cocoa Touch Framework

修改生成的Mach-O格式,因為動態庫也可以是以Framework形式存在,所以需要設置,否則默認打出來的是動態庫。將target->BuildSetting->Mach-o Type設為Static Library(默認為Dynamic Library)
Mach-o參數設置

關于底下這些參數我們可以使用默認的
架構參數設置

依賴關系<Link Binary With Libraries>:
1.制作Framework可以包含.a,也可以包含Framework<只需將Framework的.o目標集合文件拖進來>。
2.對于Cocoapods管理的FrameworkTargetSingle View Application形成的Target是有區別的,Single View Application形成的Cocoapods會為我們自動依賴libPods.a,對于Framework需要我們手動將各個模塊的.a添加進來。
3.關于第三方,需要和合作方確定好第三方的版本,對于合作方沒有的要協商好是對方給工程中去添加,還是自己在打SDK時一起打進去。

第二個就是我們配合Framework使用的Bundle

創建時需要選擇此處


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版本不同,有時是需要重新修改參數。
第三個就是我們用來檢驗FrameworkBundleDemo

對于此Target我們可以直接依賴Framework,Bundle來檢驗,這里我們只需要先各自Commad+B后直接將依賴關系添加進來就可以。

Framework添加依賴關系

Bundle添加依賴關系

你也可以在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中只包含當前架構),關于ReleaseDebug二者的區別這里不做說明,你可能會發現對于ReleaseDebug版本打出的Framework大小沒有多大變化,但是二者提供給合作方之后,對方打出的ipa大小變化是比較明顯的,我這邊相差45M的樣子,這個差值如果要讓你通過刪代碼和減小資源來彌補是一件很困難的事情。
下來我們來創建一個Aggregate
Aggregate創建

添加一個Run Scipt
添加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 DeviceCommand+B一份出來,注意區分ReleaseDebug模式。

Release和Debug模式

2.然后在相同的模式ReleaseDebug下去運行Aggregate
這個是利用腳本去打,我們自己也可以手動利用命令在終端中去實現。
當你打出來后就可以看到下面的模塊

Framework包含的模塊

你可以使用lipo -info來查看你的二進制文件包含的框架
其中核心就是.o 格式的目標集合文件,我們可以使用命令來進行查看

lipo *** -thin armv7 -output ***_armv7
ar -x

首先需要從我們剛剛打出來的包中剝離出來一種架構出來(當然你也可以只Command+B一種架構來)

剝離某一中架構

查看所有的.o文件
查看.o文件

發現這里有個__.SYMDEF文件

利用cat命令可以查看
cat __.SYMDEF

當執行完后會在終端中輸出一大串,會發現這個是我們的類的名稱,但不包含CategoryExtension的信息,但是你發現在.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中的命名,一定要按照蘋果的命名規范來,否則沖突的可能性還是很大的,一般情況下對于類名大家都能做到規范,但是對于CatergoryExtention或者 extern等,就時常不太嚴謹,此時如果恰好方法名重復,就會造成方法實現替代的沖突,對于這種情況是發生在運行時的,也就是說如果測試沒有覆蓋到則可能將此問題附帶上線。

我們可以在工程中這樣進行搜索Catergory

項目中搜索類別

小結

1.盡量不要用xib,storyboard不同版本Xcode打包維護成本較高。
2.打包時Xcode版本盡量小于等于合作方的版本,可以避免一些宏找不到的問題。
3.同一份代碼使用不同的Xcode版本打出來的大小是不一樣的。
4.最終上線時要使用Release版的。
5.命名嚴格的按照Apple的命名規范來。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容