你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> objc.io#19#活動追蹤

objc.io#19#活動追蹤

編輯:IOS開發基礎

追查出異步執行的代碼中出現的錯誤往往是一件非常困難的事,因為棧追蹤 (stack trace) 局限於發生崩潰的線程,這就意味著你無法獲得全部的上下文信息。與此同時,編寫異步代碼卻得益於 libdispatch,運行隊列 (operation queues) 和 XPC 提供的 API 變得越發的簡單。

活動追蹤是一項 iOS 8 和 OS X 10.10 新引入的技術,它正是為了幫助我們更好地解決上面提到的這個問題。今年的 WWDC 有一個有關這個主題非常棒的 session,不過我們認為在這裡為它另外寫一篇簡介也是一個好主意,畢竟知道它的人還不多。

活動追蹤技術最基本的思想是,用一個活動匯集響應某個用戶交互行為或其他事件的全部工作,無論這些工作是同步的執行的還是被分配到其他隊列和進程中去的。舉個例子來說,如果用戶在你的應用中觸發了一個刷新然後崩潰了,即使這個崩潰是發生在其他的隊列中或者有其他的代碼路徑也能運行到崩潰的代碼,但你總能知道就是“刷新”這個特定的用戶行為造成了接下來發生的崩潰。

活動追蹤由三部分組成:活動,面包屑 (譯者注:參考面包屑導航) 和追蹤信息。我們會在接下來詳細探討這三部分,在這裡先給出要點:活動可以在跨隊列跨進程的情況下幫助你追蹤出導致崩潰發生的事件。面包屑可以幫你跨越多個活動畫出導致崩潰發生的事件軌跡。最後,追蹤信息可以幫助你為當前活動添加更詳細的信息。出現任何問題,這三種信息都會出現在最終的崩潰報告中。

在我們進入細節講解之前,讓我簡單的提一下使用活動追蹤過程中可能的坑:如果活動追蹤信息沒有顯示出來,檢查 system.log 中是否有 diagnosticd 這個守護進程 (譯者注:參考守護進程) 給出的類似 "Signature Validation Failed" 的信息,你可以會遇到代碼簽名的問題。此外,注意在 iOS 上活動追蹤只能在真機上工作,在模擬器上不行。

活動

活動是這項新技術的核心部分。一個活動匯集了響應一個事件所需的全部代碼運行過程,而無論代碼是運行在哪個隊列哪個進程中的。這樣在代碼運行過程中出現任何問題,都可以輕易地追蹤回造成崩潰的源頭的事件。

活動追蹤集成在 AppKit 和 UIKit 中,所以每當一個用戶界面事件通過 target-action 機制傳遞,一個活動會自動開啟。對於不會向響應鏈發送事件的用戶交互,例如點擊 table view cell,你就需要自己手動初始化一個活動。

啟動一個活動非常簡單:

#import os_activity_initiate("activity name", OS_ACTIVITY_FLAG_DEFAULT, ^{  
    // do some work...
});

這個 API 會同步地執行 block,在 block 中運行的任何代碼,即使是那些你分配到其他隊列上去運行的工作或者進行了 XPC 調用,都會被歸為這個活動中。第一個參數是活動的標記,必須賦予一個字符串常量 (活動追蹤 API 中其他字符串參數也都必須是常量)。

第二個參數,OS_ACTIVITY_FLAG_DEFAULT,是你要新建一個全新活動的標識。如果你想在一個已經存在的活動中創建一個新的活動,那麼必須使用 OS_ACTIVITY_FLAG_DETACHED。舉例來說,當響應一個用戶交互控件發送的行為信息時,AppKit 已經為你開啟一個活動。如果你想在這個用戶交互響應沒有完成的時候開啟一個活動,你就需要一個分離的活動。

There are other variants of this API that work in the same away — a function-based one (os_activity_initiate_f), and one that consists of a pair of macros:

這個 API 有幾個同樣功能的變體,一個基於函數 (os_activity_initiate_f),一個由幾個宏命令組成:

os_activity_t activity = os_activity_start("label", OS_ACTIVITY_FLAG_DEFAULT);  
// do some work...
os_activity_end(activity);

注意你必須設置至少一個追蹤信息,否則活動就不會出現在崩潰報告或者其他任何形式的查看方式中。在後面會有更多有關追蹤信息的細節。

面包屑

面包屑所起到的作用就像它的名字所暗示的那樣:當代碼出現崩潰,你的代碼會留下一個可以說明上下文信息的跨活動事件標記軌跡。添加面包屑非常的簡單:

os_activity_set_breadcrumb("event description");

事件被存儲在一個環形的緩沖區內,這個緩沖區只記錄最近運行的50個事件。所以這個 API 只應該用來標記宏觀的事件,比如像特定的用戶交互行為。

注意調用這個 API 只能在一個活動過程當中時有效:將當前這個活動標記為一個面包屑。這也意味著每一個活動你只能標記一次,接下來的調用會被系統忽略。

追蹤信息

追蹤信息用來為活動添加附加的說明信息,就跟你使用 log 信息的目的一樣。你可以用來向崩潰報告添加一些重要的說明信息,這樣就可以使你更容易理解造成問題出現的根源。在一個活動中,一個簡單的追蹤信息可以這樣添加:

#import os_trace("my message");

除此之外,追蹤信息可以起到更大的作用。os_trace 的第一個參數是一個格式化字符串,和你在 printf 或 NSLog 中的用法一樣。不過 os_trace 有一些限制:格式化字符串最長只能包含 100 個字符和最多 7 個標量值的占位符。這就意味著你不能輸出字符串,如果你嘗試輸出字符串,字符串會被一個占位符所替換。

下面是兩個在 os_trace 中使用格式化字符串的例子:

os_trace("Received %d creates, %d updates, %d deletes", created, updated, deleted);  
os_trace("Processed %d records in %g seconds", count, time);

我在嘗試使用這個 API 時偶然發現了一個坑,那就是如果沒有從發生崩潰的線程中發送出來追蹤信息,那麼所有的追蹤信息都不會出現在崩潰報告中。

追蹤信息的變體

在 os_trace 的基礎上有好幾個功能相似的 API。首先是 os_trace_debug,可以用來只在調試模式下輸出追蹤信息。在生產環境中這會很有幫助,可以減少環形緩沖區存儲的無用追蹤信息,這樣你就可以得到最有價值的那些信息。要開啟調試模式,需要設置環境變量 OS_ACTIVITY_MODE 為 debug。

除此之外,還有兩個功能相近的宏命令可以輸出追蹤信息:os_trace_error 和 os_trace_fault。前者可以用來表明出現了未預料到的錯誤,後者用來表明出現了致命的錯誤,比如說馬上就要崩潰了。

前面我們提到了,標准的 os_trace API 只接受一個限制長度和標量類型的格式化字符串。這樣的設計是基於對隱私性,安全性和運行效率的考慮。但是,當你在調試一個問題的時候,總有一些情況下會想要得到更多的數據。這個時候帶有負載 (payload) 的追蹤信息就派上了用場了。

帶有負載的追蹤信息對應的 API 是 os_trace_with_payload,初看可能會有些奇怪:它類似於 os_trace,這個 API 接收一個格式化字符串,一個可變數量的參數值,和一個帶有 xpc_object_t 類型參數的 block。這個 block 在生產環境中不會被調用,也就不會產生額外開銷。不同的是,在調試的過程中你可以在 block 唯一的字典參數中存儲任何你想要的數據。

os_trace_with_payload("logged in: %d", guid, ^(xpc_object_t xdict) {  
    xpc_dictionary_set_string(xdict, "name", username);
});

這個 block 的參數類型之所以是 XPC 對象,是因為在活動追蹤技術的底層使用了 diagnosticd 守護進程收集數據。通過 API xpc_dictionary_set_* 設置這個字典對象的值就是在與這個進程進行通信。你可以通過命令行工具 ostraceutil 來查看負載的數據,關於這個工具我們接下來會深入探討。

我們之前討論的所有與 os_trace 相似的宏命令都可以進行負載。與上面使用的 os_trace_with_payload 很像,我們還有 os_trace_debug_with_payload,os_trace_error_with_payload,和 os_trace_fault_with_payload。

查看活動追蹤的進行

除了崩潰報告之外你還有兩種辦法可以查看活動追蹤的輸出。首先,活動追蹤集成在調試器中。在 LLDB 的控制台中輸入 thread info,你可以得到當前線程中當前進行的活動和追蹤的信息。

(lldb) thread info
thread #1: tid = 0x19514a, 0x000000010000125b ActivityTracing2`__24-[ViewController crash:]_block_invoke_4(.block_descriptor=) + 27 at ViewController.m:26, queue = 'com.apple.main-thread', activity = 'crash button pressed', 1 messages, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
  Activity 'crash button pressed', 0x8e700000005
  Current Breadcrumb: button pressed
  1 trace messages:
    message1

另一種選擇是使用命令行工具 ostraceutil,在終端中運行

sudo ostraceutil -diagnostic -process  -quiet

(將替換為進程 id) 會輸出如下信息 (有縮減)

Process:  
==================
PID: 16992  
Image_uuid: FE5A6C31-8710-330A-9203-CA56366876E6  
Image_path: [...]
Application Breadcrumbs:  
==================
Timestamp: 59740.861604, Breadcrumb ID = 6768, Name = 'Opened theme picker', Activity ID: 0x000008e700000001  
Timestamp: 59742.202451, Breadcrumb ID = 6788, Name = 'button pressed', Activity ID: 0x000008e700000005
Activity:  
==================
Activity ID: 0x000008e700000005  
Activity Name: crash button pressed  
Image Path: [...]  
Image UUID: FE5A6C31-8710-330A-9203-CA56366876E6  
Offset: 0x1031  
Timestamp: 59742.202350  
Reason: none detected
Messages:  
==================
Timestamp: 59742.202508  
FAULT  
Activity ID: 0x000008e700000005  
Trace ID: 0x0000c10000001ac0  
Thread: 0x1951a8  
Image UUID: FE5A6C31-8710-330A-9203-CA56366876E6  
Image Path: [...]  
Offset: 0x118d  
Message: 'payload message'  
----------------------
Timestamp: 59742.202508  
RELEASE  
Trace ID: 0x0000010000001aad  
Offset: 0x114c  
Message: 'message2'  
----------------------
Timestamp: 59742.202350  
RELEASE  
Trace ID: 0x0000010000001aa4  
Thread: 0x19514a  
Offset: 0x10b2  
Message: 'message1'

真正的輸出會比 LLDB 控制台的輸出多很多,因為還包括了面包屑軌跡和所有線程的追蹤信息。

除了使用 ostraceutil 的 -diagnostic 參數之外,我們還可以使用 -watch 參數動態地查看追蹤信息和面包屑的輸出。在這種模式下,還會輸出追蹤信息的負載數據。

[...]
----------------------
Timestamp: 60059.327207  
FAULT  
Trace ID: 0x0000c10000001ac0  
Offset: 0x118d  
Message: 'payload message'  
Payload: ' { count = 1, contents =  
    "test-key" =>  { length = 10, contents = "test-value" }
}'
----------------------
[...]

活動追蹤和 Swift

在寫下這篇文章的時候,Swift 還不能直接調用活動追蹤 API。

如果你想在一個 Swift 項目中使用活動追蹤,就必須創建一個 Objective-C 的封裝,使得 Swift 可以通過橋接頭文件 (bridging header) 來調用那些 API。注意活動追蹤那些宏命令要求字符串是常量,也就是說你不能直接用封裝函數的參數做活動追蹤 API 的參數。為了說明這一點,下面這樣的調用是不起作用的:

void sendTraceMessage(const char *msg) {  
    os_trace(msg); // this doesn't work!
}

一種可能的解決方法是定義特定的輔助函數像這樣:

void traceLogin(int guid) {  
    os_trace("Login: %d", guid);
}

總結

活動追蹤作為一個新的調試工具非常受歡迎,它使得我們更容易診斷異步代碼中出現的錯誤。我們都應該將標記活動面包屑和追蹤信息作為編碼的新習慣。

對於那些已經在正式生產環境中使用 Swift 的人來說,目前最大的遺憾是 Swift 不能夠直接調用。希望 Swift 的集成只是一個不會很久遠的時間問題。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved