VOOZH about

URL: https://read01.com/RRB7dP.html

⇱ 如何編寫一個受歡迎的 iOS SDK - 壹讀


Sunday, Apr 12, 2026

如何編寫一個受歡迎的 iOS SDK

2016/07/29 來源:東方網

Conrad Kramer 作為 Workflow 團隊的一員,他一直在維護著項目當中所使用的依賴庫和 SDK,同時他也一直在向開源項目貢獻代碼。在本次AltConf 2016講演當中,Conrad 向大家分享了他在維護工程中所汲取到的經驗教訓,他還講述了是何種因素讓 iOS(以及其他)SDK 變得如此好用的,此外他還會介紹如何在創建一個新的 SDK 的時候避開一些常見的陷阱。他所講演的內容涵蓋了程式語言、構建系統、編譯環境、運行平台等多個方面,其中還有很多您很可能不知道的內容,Conrad 向我們展示了一次非常寶貴的講演,他將教導您如何以最有用、最友好的方式同他人分享您的代碼。

大家好!我是Conrad。在做的不少人都會持有這麼一個觀點:編寫一個 iOS SDK 就同喝水一樣容易,只需要將代碼扔到項目當中,然後編譯一下,好的,萬事大吉!然而,千里之堤,潰於蟻穴,這就是為什麼我在標題那裡加了「不」這個詞語,搞不好您的 SDK 就會被眾人所厭煩;下面我將要講述在構建一個 iOS SDK 的時候,什麼是該做的,什麼是不該做的,這將會是一個很長的清單。

我現在在為一款名為Workflow的應用工作,在應用當中,我們使用了大量的第三方應用和服務。因此,我們在應用當中集成了大量的第三方 SDK。我們大概有將近 30 個子模塊。其中有很多 SDK 我們需要將它們 fork 出來,以維持一個穩定的版本,甚至有些我們還最要編寫自己的版本,其原因歸結於它們的原始功能並不滿足我們的需求。

但是我們和其他開發者所不同的是,我們會真正用心地去改善這些 SDK。而其他開發者和我們的做法不同,如果您的代碼不滿足他們的需要的話,他們會直接把你的代碼 pass 掉,因此您需要讓您的 SDK 支持多種不同的配置條件。

另外,有一些 SDK 並沒有開源,因此如果這些 SDK 不滿足開發者需求的話,因為他們根本沒法對其進行定製;只要他們想,他們完全會放棄使用您的 SDK。

本次講演的一個中心內容就是:「不要想當然」。因為開發者們的需求千變萬化,因此您需要讓您的 SDK 足夠靈活,以便能夠完全處理他們的需求。

為了幫助您做到這一點,我將例舉一些常用的方式來讓您的 SDK 儘可能地靈活。

使用現代化的 iOS SDK 程式語言

最重要的一點就是您所使用的程式語言。如今,編寫 iOS 應用有兩種流行的語言,我很肯定在座的各位沒有不知道的(Swift 和 Objective-C)。某些開發者只選擇使用 Objective-C,而有些開發者只選擇使用 Swift,當然絕大多數都是兩者混用。因此關鍵在於,您應該使用 Objective-C 來編寫您的 SDK,這樣沒有使用 Swift 的開發者也能夠使用您的 SDK。

同樣重要的是,您應當在項目當中添加 Objective-C 可空性 (Nullability) 和泛型 (Generics) 特性,這樣您的項目也能很輕易地使用 Swift SDK。例如,最近新版本的Dropbox SDK是完全用 Swift 編寫的,這時候我們發現我們根本沒法將其完全集成到 Workflow 當中。我們並沒有那麼多時間將其重寫為 Objective-C 版本的。

此外還有一點,如果您希望為 Swift 開發者提供一流的 SDK 支持的話,那麼您應當給您的 Objective-C SDK 編寫一層封裝,以便讓您的 API 更加 Swift 化。您同樣應該支持所有活躍的 Swift 版本,目前是 Swift 2 和 3。目前一個很好的例子便是Realm SDK,他們使用 Swift 在他們的 Objective-C 核心中編寫了一層封裝,這使得它們的 API 變得非常的Swift 化。

SDK 開發過程中,構建系統 (Build System) (我認為)是極為重要的一環。支持所有的構建系統是非常重要的,這樣所有人都可以將您的 SDK 集成進去。例如,最重要的一點就是擁有一個 Xcode 項目。

您應當擁有一個已經同時編譯了靜態庫和動態框架的 Xcode 項目,這樣人們就可以直接將您的 Xcode 項目拖曳到他們的項目當中,然後完成應用的編譯。

您同樣也應該讓您的動態框架方案 (scheme) 的狀態變為「已共享」(shared),然後在 Github 上使用語義化版本號(Sematic Versioning)標記您的發布版本,這樣您就完成了Carthage的支持。如果您不熟悉 Carthage 的話,其實很簡單,它主要是用 來完成框架的構建。

同樣,擁有podspec也是非常重要的,因為有很多人選擇使用CocoaPods,您同樣也會希望這些開發者使用您的 SDK 的。

最後我們需要支持Swift Package Manager。這是一個非常新穎的玩意兒,但是實際上,開發團隊最近增加了對 C、Objective-C 以及 C++ 的支持,因此,現在您可以選擇使用 Swift 3 來創立一個Package.swift 文件,然後指明如何構建 SDK,這樣您就可以驕傲地說:我的 SDK 現已支持 SPM。

SDK 能夠運行編譯的環境同樣也是非常重要的。編譯環境和運行平台有所不同:編譯環境決定了它所使用過程當中的上下文環境。這兩者還是有細微差別的,不過由於應用擴展正變得越來越重要,它正成為 iOS 的核心體驗之一,因此您需要確保您的 SDK 能夠在應用擴展當中正常工作,就如同在主應用當中一樣。

這意味著您需要讓您的文件路徑可以進行配置。如果您需要讓 SDK 能夠在應用擴展當中使用,如果您需要在 或者 之間傳遞數據的話,那麼這兩個目錄的路徑不能夠使用硬編碼 (hardcode) 操作。您需要確保開發者如果有需求的話,他們能夠將文件放入到 App Groups 當中。

此外,如果人們不關心 App Group 的話,那麼您需要添加一個合理的默認值。

另外,避免與 建立依賴。這是非常重要的一點,因為應用擴展當中不存在 。如果您真的需要這麼做的話,您可以將使用了 的 API 標記為 ,這意味著您的代碼仍然能夠在應用擴展中通過編譯,只不過您沒法在應用擴展中使用這些標記過的 API 而已。

最後,如果您需要使用 來執行後台任務的話,您可以選擇使用另一個新的名為的 API。它其中包含有一個 expiring task API,允許您申請大概三分鐘的後台運行時間,最重要的是它能夠同時在主應用和應用擴展當中使用。

接下來,我們將討論一下進程間通信 (Inter-Process Communication) 和協調器 (Coordination) 相關的內容,這是非常難的一個部分。我們的想法是,如果您的 SDK 需要在主應用和 Today 應用擴展當中存儲數據的話,那麼您應該不希望它們之間發生衝突。例如,如果您有一個日誌 SDK,它會將相關時間記錄在一個文件當中,此外您又同時希望能夠在主應用和應用擴展之間共享這個文件,那麼主應用很可能會一遍又一遍地覆蓋掉應用擴展的日誌,反之亦然。

通常情況下,主應用和應用擴展都會同時運行。例如,當您在應用打開的情況下,滑出通知中心,這樣您的 Today 部件就會開始啟動,這樣您就有兩個同時運行的進程了。在這種情況下,您不能使用,因為應用擁有自己的 機制,而擴展也擁有自己的 機制,它們之間是無法互相通信的。因此,就算您同時添加了鎖,數據仍然還是會被覆蓋掉。

相反,處理這種情況的正確方式是使用內置在作業系統當中的特性,例如命名信號 (semaphore)、文件鎖 (file locks)、、以及。

編者按:Apple 的Thread Programming Guide和 NSHispter 的IPC 文章對這類特性有著詳細的介紹。

直到現在,這些特性都是非常底層的,並沒有很好的文檔來很好地進行說明,這是非常糟糕的,但是您仍然可以使用一些更高層的抽象來整合這些特性。在 Workflow 當中,我們編寫了一個名為的東西,它的 API 與 相互兼容。它能夠在 App Group 當中運行,因此您只需要在您的 App Group 當中將其初始化,這樣它就可以在應用擴展和主應用之間進行工作了。

當應用擴展和主應用之間發生了事件的話,我們使用這個 API 來進行通知操作。

支持所有的 Apple 平台(iOS、macOS、tvOS、watchOS、carOS)

支持所有平台也是非常重要的。現如今已經有很多平台面世了,有 watchOS、tvOS、iOS、macOS,可能以後還會有 carOS。如果您構建的 SDK 是網絡模塊的話,那麼沒有理由不去支持 tvOS。tvOS 和 iOS 是非常相似的,因此如果您的 SDK 能夠在 iOS 上運行,那麼它很可能也可以在 tvOS 上運行,所以您應當在 tvOS 進行構建和測試,以確保它同時也能夠支持這個平台。

在支持的過程中,通常發生的問題往往是和依賴庫相關的。如果您的 SDK 依賴於其他的第三方庫,那麼您必須進行更新,以便讓它們能夠在 watchOS 上運轉,或者添加 tvOS 支持,這些操作通常是非常簡單的,但是也會讓人非常沮喪。例如,如果您的 SDK 有一個 iOS 特性的 UI,但是其中的網絡核心庫和其他分析庫的做法是類似的,那麼您可以選擇將 UI 編譯變為可選,這樣就可以同時支持 Mac 了。

通常的經驗告訴我們,至少要支持上一個 iOS 版本,因為絕大多數應用都需要支持之前的版本。現在您至少需要支持 iOS 8 或者 iOS 9。不過,如果不麻煩的話,您應該儘可能地支持更早期的版本。

比如說,如果您只用到了 的話,那麼您應該至少能夠支持到 iOS7 或者 macOS 10.9 的版本。

接下來,讓我們談談依賴庫這個玩意兒。在絕大多數情況下,您都需要避開強依賴的出現,關於這一點有著數不勝數的原因可以解釋這一點,但最主要的理由在於:這可以讓您的 SDK 更輕量、更靈活。

如果您想要修改您的 SDK,或者有任何新的平台出現,或者諸如此類的事情發生了,那麼不建立強依賴關係可以讓您沒必要進入到 SDK 當中,從而確保所有的依賴都同樣進行了更新。

對於 Swift 來說,最容易出現的問題就是代碼級別的兼容性問題了。如果您使用 Swift 3 寫一個封裝,並且它仍然依賴於某個仍然使用 Swift 2 寫的東西的話,那麼您不得不將所有東西全部升級到 Swift 3。而如果您只使用到了這個 SDK 的某一小部分的話,那麼您這樣做基本就是在浪費時間、浪費青春,我相信大家是不樂意這麼做的。

因此,您需要儘可能避免使用封裝,例如 keychain 的封裝、Alamofire或者AFNetworking之類。因為當您對您的 SDK 進行更新的時候,依賴庫往往還沒有進行更新。此外,它們往往還會提供很多沒有必要的功能,您很可能只使用其中的一小部分,所以最好的做法就是自行實現這一部分。

為您的 API 編寫文檔

此外,您需要為您的 API 編寫詳細的文檔,但是這不僅僅只是需要使用 Xcode 來為您的方法編寫使用說明,您同樣還需要在 README 當中添加對 SDK 的綜述和概覽。您需要確保開發者能夠理解相關的基本概念,讓他們知道如何將您的 SDK 整合到項目當中,並且知道如何進行配置,這兩個方面都是非常重要的。

我經常要看一下 SDK 的原始碼,以便切實知道這個方法執行了哪些操作,如果它提供了詳細的文檔的話,這樣我就不用去查看原始碼就知道它執行了哪些操作了,這無疑是一件非常贊的事情。並且如果您添加了諸如 Nullability 之類的修飾符,這同樣也能夠指明我是否能夠向這個方法中傳遞 值。

雖然測試是一件非常難、非常惱人的操作,但是這對開源項目來說是非常有用的。測試讓您有信心將社區提交的貢獻代碼進行合併操作,而無需擔心會有錯誤發生。這意味著您可以快速合併大量的提交請求,從而吸引一大批開發者加入到您的社區當中。例如,ComponentKit的測試庫是非常龐大的。他們使用快照測試(snapshot test) 來測試每個單獨的組件。所以,一旦有任何變化或者錯誤,那麼您就會很快地發現它。這使得提交請求合併變得更加簡單了,因為我們已經知道了沒有錯誤發生了。

此外,和持續集成 (continuous integration)關聯可以讓測試更加天衣無縫。

優化並傳達 SDK 的性能指標

為了幫助使用您第三方庫的用戶了解到他們所調用的性能耗費,您需要記錄下那些性能差的 API 調用。例如,如果您的某個方法當中需要輸出文件、上傳某個東西、處理圖片或者加載網頁內容的話,那麼您一定要告訴開發者您的操作。不然的話,他們很可能會將這段代碼放到某個快速循環當中,或者放到某段性能至上的代碼當中,但是由於您沒有告知他們,因此他們往往並不會意識到自己在做什麼錯誤的事情。

另外,如果您執行了很多處理工作的話,除非這些工作非常重要,否則請確保將它們放到了一個 GCD 隊列當中,並讓它們具備低後台優先級。例如,日誌記錄庫只應當在後台隊列當中工作,它只能在這個非常低優先級的後台隊列當中訪問網絡或者訪問某個文件,因為您不會希望它影響到您應用的主線程操作。

另一個您很可能不會考慮到的就是內存占用率了。這個是比較困難的,因為您很難在內存受限的環境中測試您的 SDK,但是現在在 iOS 平台上這種情況出現了很多。Widget 曾經只能夠使用 16M 的內存空間,雖然現在漲到了 26M,但是這仍然不會像主應用那樣擁有 650M 的內存使用空間。

因此如果您在 中加載了很多東西或者使用了 、使用了 、 之類的東西,這往往會導致您的進程發生崩潰,而您往往無法意識到錯誤的原因。如果您嘗試在 Widget 當中將一個大圖像渲染到位圖上下文環境中,那麼它就會立即崩潰。

將這些建議組合在一起:WFOAuth2

將這些建議組合在一起,要知道我已經說了很多了,但是我其實想要實現類似這樣的東西,因此我寫了一個名為WFOAuth2的庫。WFOAuth2 支持 watchOS、macOS、iOS 和 tvOS。它支持 CocoaPods、Carthage 和 Swift Package Manager,我們在 Workflow 當中用它來執行所有的身份認證操作。

我們使用 WFOAuth2 來進行 Slack、Dropbox、Box 以及其他不同服務的身份認證。它同樣也擁有完善的文檔,輕量級,沒有很多的依賴庫。

問:您此前是否使用過從 AWS API 或者其他網絡服務 API 所自動生成的 SDK 呢?

Conrad:實際上我並沒有太多的使用經驗,但是只要這些工具所生成的 SDK 沒有違背我們所討論的這幾點的話,那麼我覺得是完全沒問題的。

問:您是否推薦在框架中加入一個靜態庫,以便能夠同時支持 Carthage 和 CocoaPods?

Conrad:當然。如果您有一個開源的代碼庫的話,您當然可以直接提供二進位文件,但是我覺得您同樣也可以提供一個 Xcode 項目,這樣就可以允許開發者從中自行構建靜態庫或者框架。

問:Swift 是否支持分發閉源的框架,就如同二進位文件那樣,而無論代碼本身是否是開源的?

Conrad:可以。如果您沒有開源庫的話,那麼您所需要做的就是分發一個二進位文件。然而,這樣做的話就會比較麻煩,因為您需要分別為 Swift 2 和 3 提供不同的版本。

問:您對通過 CocoaPods 或者 Carthage 分發私有倉庫有沒有什麼建議呢?

Conrad:您可以輕鬆地在CocoaPods或者 Carthage 當中包含私有倉庫。對於同時維護相同的公開和私有倉庫來說,我並沒有太多的經驗,但是我覺得您應該可以同時擁有兩個不同的倉庫,並且同時使用。

問:您為什麼選擇在 Workflow 中使用不支持 Carthage 或者 CocoaPods 的子模塊呢?

Conrad:之所以使用子模塊 (sub-modules) 的主要原因在於:我們需要極高的靈活性,並且我們不能時不時地用一下別的東西。我們最終選擇使用了大量不支持 CocoaPods 或者 Carthage 的第三方庫。我們使用 Carthage 來幫助構建那些我們不希望在每次運行時候都要構建的東西,但是這仍然對靈活性有影響,而子模塊則提供了我們所需要的這種靈活性。

我們所使用的一些框架甚至連 Xcode 項目都沒有,因此我們必須將源文件拖曳到我們的項目當中,然後將其作為項目的一部分進行編譯。如果我們能夠使用構建系統的話那將是極好的,但是隨著我們整合進來的東西越來越多,導致現在這樣做就不可行了。

問:您覺得有沒有 SDK 是能作為榜樣舉例的,或者說有哪些 SDK 遵循了良好的公約和標準?

Cornad:有一些 SDK 我是非常喜歡使用的。Mantle很好地遵循了我們所說的這些標準,但是它是專門為 Objective-C 而生的。儘管如此,在構建系統方面,它使用了 Xcode 和框架來進行構建,因此它非常的輕量級。它允許我們無需耗費太多精力就能夠在 watchOS 上使用它。

Realm 同樣也是一個很好的例子,它支持所有的平台以及所有版本的 Swift。

不過對於人們來說,差勁的東西反而更容易被記住。我們在使用 AFNetworking 的時候遇到了許多依賴方面的問題。因為有些第三方庫使用了 AFNetworking 的靜態庫,一旦當我們想要使用別的也用了 AFNetworking 的庫時,他們之間就會發生衝突。

對了,我突然又想起來一個用起來很開心的 SDK,那就是MailCore。儘管它是由一堆 C++ 核心代碼堆砌而成的,但是它支持在 iOS、Mac、Android 等等平台上進行編譯,從而簡化發送郵件的流程。

問:您推薦為 Objective-C 內核組件編寫一個 Swift 封裝。那麼您對 Swift 內核組件編寫一個 Objective-C 封裝是怎麼看的呢?

Conrad:您當然可以做到這一點,使用 Swift 編寫框架,然後讓其和 Objective-C 兼容。但是很不幸的是,這會導致您的框架當中被嵌入 Swift 運行時,而這很可能是別人不想要的。在 Workflow 當中,我們現在還不需要 Swift 運行時,因此我們就沒辦法使用 Swift SDK。它只是能夠歸結為支持所有的這些配置而已。以後 Swift 運行時很可能會被內置到系統當中,而那個時候,Swift 編寫的東西就能夠很好的在 Objective-C 上運行了,這是完全可行的。

See the discussion onHacker News.

Conrad Kramer

Conrad Kramer 是自動化應用Workflow幕後的 iOS 開發者。6 年之前他開始開發 iOS 設備越獄 App,從那之後他便開始了他的開發生涯。他也經常向多個開源項目貢獻代碼,其中包括了ComponentKit和Mantle,此外他自己也發布了一些開源項目。平日裡他喜歡騎車兜風、聽電音、坐禪和小憩片刻。

您可能感興趣
免責聲明:本文內容來源于東方網,文章觀點不代表壹讀立場,如若侵犯到您的權益,或涉不實謠言,敬請向我們提出檢舉
最新文章 / 服務條款 / 私隱保護 / DMCA / 聯絡我們

壹讀/READ01.COM