最近工作需求都是開發SDK提供給Unity游戲端使用,由于很長一段時間沒有進行SDK開發,很多細節都已經忘記了,此次又重新拾起來,并記錄一下流程要點。
既然我們已經從零到一完成了 App 的開發工作,那這次不妨來看看 FinClip 移動端工程師整理的如何編寫屬于我們的第一個 SDK 吧!大家也可以回顧下 iOS 開發 App 的教程。
App 的開發更偏向于用戶層面,從 UI 展示到業務邏輯處理,全程處理用戶的行為。而 SDK 面向的是開發者,開發更偏向于功能方面,注重功能的開發實現。在今天的文章中,我們一起來聊聊設計 SDK 的那些事。
一、什么是 SDK?
SDK 全稱 Software Development Kit,廣義上的 SDK 是為特定的軟件包、軟件框架、硬件平臺、操作系統等建立應用程序時所使用的開發工具的集合(在 iOS 項目中,SDK 也被稱為庫)。
在 iOS 開發或 Android 開發中,不可避免會需要使用第三方工具提升產品的開發效率,比如用于消息推送的極光,用于第三方支付與登錄的支付寶,微信等等。但大多數商用產品都不會直接給出源碼(可能只有為愛發電的開源項目才會無私提供源碼),而我們在開發 App 時就需要將這些第三方 SDK 集成在我們的項目之中。簡單來說SDK就是提供開開發者的一個工具,使其實現所需的功能。
二、SDK 設計的基本原則
一款好用且設計充分的 SDK 必須要遵循以下 4 條基本原則,即:
1、SDK 安全,穩定
2、統一的開發規范
3、Library 小而精
4、不依賴第三方 SDK
安全,穩定:考慮到 SDK 是需要嵌入到 App 里面去的,所以 SDK 最重要的特性就是安全性,不會因為亂開放接口而導致 App 數據泄露;其次重要的是 SDK 的穩定性, SDK 的 Crash 如果沒有被捕獲進行處理,則會導致應用徹底崩潰(這樣就會導致第三方接入的 App 體驗性非常差),甚至會直接導致接入方的用戶流失;
統一的開發規范:對于 SDK 開發規范來說,統一的命名規范很重要,最好的狀態是“接入方看到接口命名就能知道是哪家廠商的 SDK”,換句話說就是 SDK 的命名規范統一,形成自己公司的品牌效應,此外也方便開發者進行接入使用。此外也需要具有自己的編碼規范,你可以在網上找到大廠的規范模板,并通過借鑒整理出屬于自己的規范,從而盡早統一代碼風格;
Library 小而精:小是指要避免造成接入方的App增加很大,不然會引起接入方的不滿,甚至下架。精是指功能要專注,比如極光推送,就是專注推送相關的功能;
不依賴第三方 SDK:這個也很好理解,SDK 中如果又依賴其他第三方 SDK, 不僅會導致 SDK 的體積變大,也會影響接入方集成 SDK 的相關成本。
三、在 iOS 環境下開發 SDK
1. iOS 環境下的 SDK
如同上文所說,在 iOS 開發中,我們將 SDK 稱為“庫”,我們是這樣對其定義的:
一般是給應用提供通用服務的,非獨立運行的程序集合;
一般都是編譯過的,方便使用。
我們會根據庫的調用方法分為“靜態庫”和“動態庫”兩種:
靜態連接:一般是指在創建應用程序的時候,將庫集成進去,這樣做的好處就是應用程序包自身可以獨立運行,而不好的地方就是包會略顯臃腫,庫不能共享(靜態庫經常以 .a 結尾);
動態連接:創建應用的時候只約定好與庫之間的調用關系,而不徹底將庫包集成進應用。這樣在應用運行時,需要運行環境中提供庫,并且連接裝載。優劣與靜態庫相反,動態鏈接庫需要庫環境,但由于本身不集成庫內容,會比較小,同時也為和其他應用共享庫的使用提供了可能(常見的動態庫是 Windows 下的 .dll,Linux 下的 .so,Mac 下的 .dylib/.tbd)。
特別注意:平時我們經常說的 Framework (in Apple) 是 Cocoa/Cocoa Touch 程序中使用的一種資源打包方式,可以將代碼文件、頭文件、資源文件、說明文檔等集中在一起,方便開發者使用。也就是說我們的 Framework 其實是資源打包的方式,和靜態庫動態庫的本質是沒有什么關系。
2. 靜態庫和動態庫的區別
如果說要找出靜態庫與動態庫的區別,那可以從文件鏈接(每個源代碼模塊獨立編譯,然后按照需要將他們組裝起來,這個組裝模塊的過程,就是鏈接)的角度進行解釋:
靜態庫:鏈接時會被完整的復制到可執行文件中,所以如果兩個程序都用了某個靜態庫,那么每個二進制可執行文件里面,都會含有這份靜態庫的代碼;
動態庫:鏈接時不復制,而是在程序啟動后動態加載,然后再進行符號決議(符號綁定)。理論上動態庫只存在一份就可以了。其他的程序都可以動態鏈接到這個動態庫上面,從而節省內存(內存中只有一份動態庫)。另外一個好處是,由于動態庫并不綁定到可執行程序上,所以我們想升級這個動態庫就很容易,windows和linux上面一般插件和模塊機制都是這樣實現的。
具體的優劣勢可以看這張表:
3. 了解 iOS 的動態庫(即被閹割的動態庫)
有一個背景知識需要注意,iOS 官方規定不允許存在動態庫,并且所有的 IPA 都需要經過 Apple 的私鑰加密后才能用,即使你用了動態庫也會因為簽名錯誤而無法加載(越獄和非 App Store 除外)。于是這就把開發者自己開發動態庫這件事變成為了天方夜譚。
iOS8 之前的 iOS 應用都是運行在沙盒當中的,不同程序之間不能共享代碼,并且 iOS 又是單進程運行的(也就是某一時刻只有一個進程在運行),那么即使你寫個共享庫也無法共享給他人。
而動態下載代碼又是被蘋果官方明令禁止的,也就是說動態庫的優勢完全無法發揮,所以動態庫也就沒有存在的必要了。
但是這一切問題都隨著 iOS8 發布之后的 App Extesion 特性, Swift 的誕生發生了奇妙的改變。
由于 iOS 主 App 需要和 Extension 共享代碼,Swift 語言機制也需要動態庫,于是蘋果后來提出了 Embedded Framework,這種動態庫允許 APP 和 App Extension 共享代碼(動態庫的生命被限定在一個APP進程內)。
更簡單的解釋:雖然提供了動態庫,但這是被閹割的動態庫。
盡管如此,這種動態庫(Embedded Framework) 和系統的 UIKit.Framework 還是有很大區別的。傳統的動態庫是給多個進程使用的,而這里的動態庫(Embedded Framework)是給單個進程里面多個可執行文件用的。
系統的 Framework 不再需要拷貝到目標程序中,我們自己做出來的動態庫(Embedded Framework) 哪怕是動態的,最后也還是要拷貝到 App 中(App 和 Extension 的 Bundle 是共享的)。所以蘋果沒有直接把這種 Embedded Framework稱作動態庫而是叫 Embedded Framework。
上面提到的 Swift 也有原因,在 Swift 的項目中如果要在項目中使用外部代碼,可選的方式只有兩種,一種是把代碼拷貝到工程中,另一種是用動態 Framework。使用靜態庫是不支持的。
這個問題的根本原因是, Swift 的運行庫沒有被包含在 iOS 系統中,反而會被打包進 App 中(這也是造成 Swift App 體積大的原因),靜態庫會導致最終的目標程序中包含重復的運行庫。
4. 以動態庫為例,開始制作SDK
第一步:創建 App 工程,命名為 WaterWold
第二步:關閉 WaterWold 工程,然后在 WaterWold 目錄下創建 Framework 工程,命名為 WatherWoldSDK
第三步:設置 Framework 工程的 Build Settings
第四步:關閉WatherWoldSDK工程,創建 WorkSpace,命名為 WaterWold,或者直接pod,也會生成工作區間WorkSpace
第五步:連接 Framework 工程和 App 工程
我們需要先打開 WaterWold.xcworkspace,打開后你會發現這里空空如也。
然后我們直接把需要連接的 Framework 工程(WatherWoldSDK.xcodeproj)和 App 工程(WaterWold.xcodeproj)拖進來就可以了!
第六步:把 Framework 添加到 App 工程中
有過 SDK 開發經驗的同學到這里應該已經看明白了,所謂實時聯調說白了就是用 WorkSpace 把兩個工程連接起來而已,跟 Pod 的原理有幾分相似。
第七步:給 Framework 加點功能
我們需要增加一個 WatherWoldManger 類,定義一個 test 方法,實現里面打印一句話“吃飯了嗎”。然后修改 WatherWoldManger.h 的 Target Membership 為 Public,意思為公開頭文件。
WatherWoldManger的實現如下:
- (void)test {
NSLog(@"----吃飯了嗎-----");
}
第八步:在 App 的 ViewController 調用一下 SDK 的方法
第九步:運行一下,可以發現App工程成功調用了SDK的方法
-
使用腳本合并真機、模擬器等多種架構的 Framework
第一步:添加一個 Aggregate Target
image.png
第二步:將 Aggregate Target 命名為“ WatherWoldSDK-Script”
image.png
第三步:依賴 WatherWoldSDK
image.png
第四步:添加腳本
這個腳本是通用的,各位同學直接復制粘貼即可:
# Type a script or drag a script file from your workspace to insert its path.
UNIVERSAL_OUTPUTFOLDER=../Framework/
# 創建輸出目錄,并刪除之前的framework文件
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
rm -rf "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework"
# 分別編譯模擬器和真機的Framework
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
# 定義真機、模擬器Build文件夾路徑變量
IPHONE_BUILD=${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework
SIMULATOR_BUILD=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework
# 拷貝framework到univer目錄
cp -R "${IPHONE_BUILD}" "${UNIVERSAL_OUTPUTFOLDER}/"
#cp -R "${SIMULATOR_BUILD}" "${UNIVERSAL_OUTPUTFOLDER}/"
# 定義輸出路徑變量
OUTPUT_PATH=${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework
# 合并framework,輸出最終的framework到build目錄
lipo -create "${IPHONE_BUILD}/${PROJECT_NAME}" "${SIMULATOR_BUILD}/${PROJECT_NAME}" -output "${OUTPUT_PATH}/${PROJECT_NAME}"
第五步:運行腳本
第六步:查看結果
本人在開發過程中實驗過,“5. 使用腳本合并真機、模擬器等多種架構的 Framework”這一步可直接省略,可直接選擇真機運行SDK,可直接生成Framework
6. 小貼士
- Framework 中使用 Category
在 Framework 工程的 Build Setting 中添加 -ObjC。另外,使用我們 SDK 的 App 的 Build Setting 中也要添加 -ObjC。 - Framework 支持 bitcode
如果正確按照教程,那相信你已經成功的做出了屬于自己的第一個 iOS SDK,本期教程依然基于 mac 電腦進行實現,如果你的電腦是 Windows 或者其他操作系統,還需要進行一些其他的靈活配置。