因為你用來做 Code Sign 的 Certificate 是幾個月前產生的,所以你的 App 會 Crash

這篇 blog 就是我在 2015 年四月份台北 Cocoaheads 在台上講的事情。

在過年期間,我把去年 11 月時寫的捷運轉乘這個個人的小 iOS App 改了改,在年假結束的前幾天 submit 一個版本,結果,過了一週,被 reject。

蘋果的 reviewer 說,他們只要一安裝這個 App 的 binary,就會馬上 crash;可是在我自己的開發環境中,完全無法重現這個狀況,由於過完年公司的事情就一件一件接著來,所以也就擱在一邊不管了,我到今天才知道為什麼會 crash,而且理由還頂荒唐的。

這兩天我們把公司產品 code 裡頭的一條新功能的開發分支(講 feature branch 不知道會不會比較好懂)併回主線,然後讓主線的程式進入 Jenkins build,讓公司的 QA 部門做出貨前的測試。狀況很奇妙,原本的 App 也是跑得好好的,在支線開發的過程中,也跑得好好的,但是從 Jenkins 編出來的 build,只要一跑起來,就會 crash。

遇到 crash,第一件事情當然是先看 crash log。裡頭是這樣:

Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x000000012002d088
Triggered by Thread:  0

Dyld Error Message:
  Library not loaded: @rpath/libswiftCore.dylib
  Referenced from: /private/var/mobile/Containers/Bundle/Application/190DEE89-254A-49CF-AC15-D03FE19DADA0/KKBOX.app/KKBOX
  Reason: no suitable image found.  Did find:
	/private/var/mobile/Containers/Bundle/Application/190DEE89-254A-49CF-AC15-D03FE19DADA0/KKBOX.app/Frameworks/libswiftCore.dylib: mmap() error 1 at address=0x101210000, size=0x0018C000 segment=__TEXT in Segment::map() mapping /private/var/mobile/Containers/Bundle/Application/190DEE89-254A-49CF-AC15-D03FE19DADA0/KKBOX.app/Frameworks/libswiftCore.dylib
  Dyld Version: 353.10

大意就是,我們自己的 App 在啟動的時候嘗試載入 Swift runtime(就是 libswiftCore.dylib 這個 library),但是載入 Swift runtime 時失敗,跳出錯誤訊息「no suitable image found」,因此你也無法執行接下來所有的 Swift code,你的 App 就 crash 了。

要了解這個狀況,我們先看一下 Swift 這個程式語言底下的一些運作原理。因為 Swift 與 Objective-C 語言在 Class 的 virtual table 運作方式的不同,每個用到 Swift 的 iOS App,只能夠用當時版本的 Swift runtime,所以,在 App bundle 裡頭都會有一份跟你當時 compile 程式碼時版本相同的 Swift runtime,應用程式執行時,就要載入這份 runtime。你在 iOS 裝置上裝了幾個用 Swift 寫出來的 App,裡頭就有幾份 Swift runtime。

像 Xcode 6 剛出來的時候是 Swift 1.0,你用 Xocde 6 編出來的 App 裡頭就有一份 Swift 1.0 的 runtime;這兩天 Xcode 6.3 出來,裡頭包含 Swift 1.2,那你以後編出來的 iOS App 裡頭就有 Swift 1.2 runtime。我之前在另外一篇 blog 裡頭也提到這件事情。

那,明明就是在 compile time 一起包進去的這份 runtime,會什麼反而會在執行時載入失敗?查了一下,看到了蘋果今年二月中的這份文件:QA 1886。摘錄內容如下:

Q: What can I do about the libswiftCore.dylib loading error in my device’s console that happens when I try to run my Swift language app?

A: To correct this problem, you will need to sign your app using code signing certificates with the Subject Organizational Unit (OU) set to your Team ID. All Enterprise and standard iOS developer certificates that are created after iOS 8 was released have the new Team ID field in the proper place to allow Swift language apps to run.

如果我們想要在 iOS 裝置上實機執行,都要先對 App bundle 做過 code sign,在 iOS 8 之後,App bundle 裡頭還可能包含像是 today widget 之類的 extension,每個 extension 也都要做 code sign。除此之外,其實 App bundle 裡頭的這份 Swift runtime,也是要做 code sign 的,如果沒有成功做完 code sign,App 就不能載入這份 Swift runtime。

要對 Swift runtime 做 code sign,有件事情很重要,就是 code sign 用的 這份 certificate 必須得是 iOS 8 推出之後,你才在蘋果開發者網站上建立的,因為後來蘋果在 certificate 裡頭加了 Team ID 這個欄位,如果沒這個欄位,這個 certificate 就沒辦法用來簽署 Swift runtime 的 binary。

為什麼我們在分支中開發沒有問題,上了 Jenkins 編出來的 binary 會 crash?原因很簡單,我們在這個分支當中才開始在原本的 Objective-C 專案中使用 Swift,而開發過程中,我們用的是 development 用的 certificate,而 Jenkins 上使用的是另外的內部 release 用的 certificate;開發用的 certificate 大概是這一兩個月才建立的,至於 release 用的呢,則大概都是去年六月左右產生的—如果一般 App,release 用的 certificate 可以用一年,enterprise developer program 則可以用三年,時間沒到,誰會去更新 certificate 嘛。

修理 crash 的方向居然是更新 certificate,這簡直是巫術。

所以呢,就花了一個下午的時間,更新手上每一台 Jenkins slave 上面的好幾個不同 developer program 的 certificate(這又是開發 iOS App 的另外一個問題了,一個 developer program 只能容納一百個測試裝置,你在一家三百來人的網路公司,幾年下來需要開發的、測試的人買些裝置,一百台怎麼可能夠,所以你手上就有五個 developer program),然後 trigger 一堆 build 確定每個 job 編出來的 build 都沒問題。一個下午就這麼過去了。

你瞧瞧,我自認我寫程式寫得又快又好,可是公司很奇怪,像我這樣的人都不讓我去寫程式,都叫我去開會;開完會也不是在寫程式,而是處理像 code sign 這種稀奇古怪的鳥問題。

可是,回到捷運轉乘這個 App 的狀況—我在去年 11 月上架,當時大概也是用去年六月左右產生的 release certificate 做 code sign,也是在 iOS 8 推出之後,用 iOS 8 推出之前的 certificate,為什麼就沒有事?難道 Swift runtime 需要 code sign 其實是今年才開始的?

仔細想想,在公司產品 coed 的這個功能分支的開發過程中,之前也沒發現 Swift 與 code sign 有什麼問題,原因除了前述我們用的是不同的 certificate 外,大概就是這個分支幾乎都是用模擬器開發與測試。為什麼不用實機?因為這些功能對應的裝置還沒推出哇…當然,這又是另外的故事了。

Be Sociable, Share!

Leave a Reply