Objective-C調試技巧——iOS開發者的Debug指南

語言: CN / TW / HK

theme: github

前言

  開發者們在使用Objective-C開發過程中難免會遇到各種類型的bug或難題,而熟練使用IDE工具進行調試無疑會提高開發效率,這裏我總結了常用的調試技巧。

  接下來我會從以下幾個方面逐一介紹:首先簡單介紹Xcode調試需要關注的工具條,這裏斷點工具條會被放入後續斷點相關的內容中;接下來會敍述項目隱藏漏洞排查“利器”——代碼靜態分析;然後我們會將注意力集中在斷點調試上;光有斷點還不夠,我還補充了Log的使用,在特定場景使用有意想不到的效果;作為靜態分析的對照,我簡單介紹了代碼動態分析及其使用;最後本文會提供一些調試相關的雜項內容當做補充。後續我會將Objecttive-C簡稱為OC。因為涉及面較廣,我更希望讀者當它是一個索引,根據目錄找到感興趣的內容,給到讀者一些思考啟發。

1 Xcode工具條熟悉

  工欲善其事必先利其器,在介紹OC調試前,先熟悉Xcode中左上角的工具欄,後續內容會重點關注紅框標註出的四個部分。 - 項目導航器(Project navigator):管理項目中的文件資源; - 版本控制導航器(Source Control navigator):查看分支文件修改情況以及項目倉庫信息,如分支名、暫存更改以及遠程分支狀態; - 符號導航器(Symbol navigator):項目中的可識別符號如類名、方法以及變量名都可以在這裏查看; - 文件查找導航器(Find navigator):查找項目中的某個文件以及位置; - 問題導航器(Issue navigator):可以查看項目在構建或運行時出現的問題或者警告; - 測試導航器(Test navigator):項目運行時可以查看CPU、內存以及磁盤等的佔用情況,方便後續優化,若添加斷點還可以查看堆棧調用信息; - 斷點導航器(Breakpoint navigator):方便管理項目中加入的斷點,快速定位斷點位置; - 報告導航器(Report navigator):查看項目在構建運行以及調試時的日誌信息;

2 代碼靜態分析

  代碼靜態分析通過掃描代碼,查找違反預編程規則的問題,如內存泄露和緩衝區溢出,可以有效提高代碼質量,值得注意的是Xcode中的靜態代碼分析適用對象只有C、C++、Objective-C,除此之外如代碼追蹤和性能檢測的工作應結合instruments使用,後面會介紹instruments及其使用。接下來我們會介紹如何打開代碼靜態分析的功能以及如何使用它解決常見問題。

2.1 使用方式

一般快捷鍵為 Command + Shift + B , 也可以通過Xcode 頂部工具欄選擇Analyze。

執行之後便可以在問題導航區看到問題提示,雙擊後可以直接定位到代碼處,可以看到右邊紅框的提示即為邏輯錯誤,這裏漏掉了一個父類方法的調用。值得注意的是因為是靜態分析,所以並不能檢測出所有錯誤,且存在誤報風險,比如這裏的左邊紅框提示controller4並未使用,其實最後在運行時狀態UI會將其從數組中取出並渲染;

2.2 靜態代碼分析可以解決的常見問題

  • 邏輯缺陷 可以看到這裏返回了一個未初始化的值,與此同時IDE也會提示有變量並未初始化。

  • 內存管理錯誤

    這裏檢查到返回了一個空指針,但是函數並不希望返回一個空值。這裏檢查函數編寫邏輯也存在錯誤,增加默認判斷保證返回值不為空即可。

    當程序中包含潛在的內存泄露的問題時,靜態代碼分析也可以檢測,這裏CoreGraphics框架下的CGColor需要我們手動管理內存,使用 CGColorRelease 釋放即可。

  • 無用存儲邏輯\ 這裏主要是指永遠不會訪問的變量以及永不會執行的代碼。

    這裏提示 Value stored to 'cellNum' is never read 説明代碼可以優化,直接返回需要的數值,可以去掉cellNum這個臨時變量。

  • API濫用 這裏的問題主要是忽略了一些OC對象的使用規則,數組中不允許添加空值,除此之外字典中也不允許設置key和value為空值。

  • Xcode13新增檢查特性\ 在Xcode13中靜態代碼分析器升級,可以捕獲更多的邏輯錯誤如死循環、NSAssert副作用、無用的宂餘代碼等。這裏我們以NSAssert副作用為例,編寫代碼後使用 Command + Shift + B 開啟靜態代碼分析,可以看到提示condition中的語句會在release版本下丟棄,所以不應該將變量修改的代碼放入condition中執行,這裏將對number的修改代碼移至NSAssert函數之外即可,不僅是Objective-C中的NSAssert函數,C和C++中的Assert函數也可以捕獲異常。

3 LLDB常用指令與斷點使用

  斷點一般可以分為普通斷點,條件斷點,符號斷點,異常斷點,watch斷點,線程斷點。在敍述各個斷點的使用功能之前先介紹調試區域工具欄以及常用LLDB指令。

3.1 斷點工具欄

在此我們還是先熟悉一下斷點相關的工具區及其用途。 圖中各標籤對應含義如下: - 1——斷點開關(Deactivate breakpoint),當為藍色時斷點打開,點擊後藍色消失,斷點關閉; - 2——繼續執行(Continue program execution),點擊後會跳轉至下一個斷點,若沒有則程序進入運行狀態; - 3——單步運行(Step over),單步執行程序,方便觀察每一步的運行狀況,當遇到子函數不會進入而是執行完進入下一條語句; - 4——跳入函數(Step into),進入子函數中繼續執行; - 5——跳出函數(Step out),結束子函數執行跳轉到上一級函數; - 6——層級視圖(Debug View Hierarchy),查看程序當前頁面的UI層級視圖,定位視圖組件層級關係; - 7——內存結構圖(Debug Memory Graph),直觀檢查是否存在如內存泄漏的問題; - 8——調試器外觀設置(Environment Overides),設置真機或模擬器的深淺色以及字體字號等外觀元素; - 9——模擬定位(Simulate Location),設置真機或模擬器的定位位置; - 10——斷點處實例變量信息(Variables View); - 11——LLDB終端,執行相關指令,清屏快捷鍵為 Command + K ;

3.2 常用LLDB指令

  • help指令——查看LLDB提供的指令及其作用

    當我們想查看某個具體指令的用法時可以使用 help commandName,可以看到其提示的諸多搭配用法以及含義。

  • command指令\ command指令主要用來管理自定義的一些LLDB指令,常常結合script或者source使用,command script import filePath 導入自定義的指令,而 command source 指令用於執行指定文件中的LLDB指令。LLDB會在幾個特定位置預加載一些內容,而~/.lldbinit文件屬於其中之一。若本地並無此文件可以自行創建,然後輸入以下內容並保存,其中 fblldb.py 為facebook開源的chisel提供的定製LLDB指令集合的入口文件,詳細內容以及安裝方式點擊這裏,其中也包含如何添加個人定製的指令。 command script import path/to/fblldb.py 然後打開LLDB調試窗口,輸入以下指令,如此便可以調用chisel提供的調試指令。除此之外,command source 指令還可以導入個人定製的.lldbinit文件,從而避免使用高達(Gundam)動態生成的.lldbinit文件,無法包含自定義配置項,而在.lldbinit文件中添加 command script import 指令也可以算作自定義配置的一種。 command source ~/.lldbinit

  • p指令——打印基本數據類型
  • po(print object)指令——打印對象

    除此之外po指令還能和oc中的方法結合使用。

    當自定義一個類時,調用po指令。

    這時並沒有顯示我們需要的信息,需要重寫KSEText類的debugDescription方法。

    再次執行po指令。

  • call指令——動態調用函數\ 執行指定函數,在不重新編譯的情況下更改視圖,除此之外,call指令同樣可以執行表達式

  • expr指令(expression)——執行指定的表達式\ 若執行下述操作出現報錯提示可以使用 expr videoController.tabBarItem.title = [NSString stringWithUTF8String:"短視頻"] 來解決(出錯原因)。

  • image指令——查找異常地址\ 這裏我們模擬數組越界的情況,接着使用image指令查找出錯位置(當然後面會介紹使用異常斷點直接定位),而 image list 可以查看當前項目所有引入的庫,常用於查看某個插件是否注入自己的項目。 模擬代碼如下。

    下面是出錯的提示信息,可以看到前面的CoreFoundation和libobjc.A.dylib和我們關係不太大,可以直接從序號4開始查。

    執行 image lookup -a(image lookup --address),可以看到Summary部分提示了出錯位置為AppDelegate.m文件中的53行18列。

  • bt指令——打印線程堆棧信息 bt all 打印所有線程堆棧信息

    當只需要部分堆棧信息時,可以在 bt 指令後面添加具體打印的行數信息。

3.3 斷點使用

  • 普通斷點\ 在代碼中某一行點擊最前面的數字就可以添加普通斷點,當程序執行至斷點處便暫停執行,注意斷點對應的程序語句是沒有執行的。從下圖中可以看到,程序目前暫停在了36行,且前面的斷點標籤即數字1所指的位置是亮藍色,當再次點擊圖標會變暗,狀態變為disable;

    數字2指向的按鈕功能為跳轉至下一個斷點,當後續沒有斷點時,程序會進入運行狀態,如下所示,後面的按鈕也會進入禁用狀態。

    當我們添加多個斷點想要統一管理時,便可通過左上角的斷點導航器查看,雙擊則跳轉至斷點處。普通斷點可以結合LLDB指令使用,排查代碼邏輯是否符合預期。

    而點擊左上角的調試導航器圖標則可以查看程序運行的內存CPU等資源的佔用情況,除此之外還可以查看當前線程的函數調用棧;

    如下圖所示,從左邊的堆棧調用關係可以看到,main 函數間接調用了 KSECollectionViewController的方法 [collectionView:cellForItemAtIndexPath:],然後[collectionView:cellForItemAtIndexPath:] 方法調用 GTReuseableCell的 [initWithFrame:]方法。

  • 條件斷點\ 添加條件斷點,我們可以在原先普通斷點的基礎上進行編輯修改使其成為條件斷點。右鍵斷點選擇“Edit Breakpoint...”

    在Condition一欄添加我們設想的邏輯條件,然後運行程序,當符合斷點條件時,程序便會停留在斷點處。

    當然我們還可以在符合斷點條件時執行指定的動作,點擊Add Action進入選擇界面。

    點擊紅框框選按鈕,可以看到有六個選項,各自作用分別為: - AppleScript:執行腳本文件; - Capture GPU Workload:用於OpenGL ES調試,捕獲斷點處GPU當前繪製幀; - Debugger Command:和控制枱中輸入LLDB調試命令一致; - Log Message:輸出自定義信息至控制枱; - Shell Command:接收命令文件及相應參數列表,Shell Command是異步執行的,只有勾選“Wait until done”才會等待Shell命令執行完再執行調試; - Sound:斷點觸發時播放聲音;

    除此之外,當我們在循環中設定斷點後可以使用Ignore選項跳過前面N次,直接查看需要的信息。

    而下方的Options選項選中後斷點不會終止程序執行。

  • 符號斷點\ 對一個方法下斷點,當程序調用此方法時觸發斷點,這裏可以是OC方法或者C++函數,常用於調試第三方庫,給相應函數下斷點,查看程序當執行流程。這裏添加方法相比之前並不相同,這裏先點擊左上方的斷點導航工具圖標,進入斷點查看界面。

    點擊左下方的”+“按鈕,然後選中Symbolic Breakpoint...進入符號斷點添加界面。

    在輸入符號時可以只輸入函數名,也可以按[類名 函數名]的方式輸入,還可以在前面加上類方法或者實例方法修飾符。

    - 異常斷點(全局斷點)\ 用於快速定位由於程序拋出異常而導致退出,如常見的數組越界,添加方法同符號斷點,選中斷點導航器,點擊左下角“+”號,選擇Exception Breakpoint...,隨後進入異常斷點設置界面。

    這裏我們主要配置兩個地方,Exception和Break,Exception可以選擇Objective-C、C++以及All,值得注意的是,當程序使用異常來組織框架邏輯,這時候選擇All會頻繁觸發異常,所以這種情況更適合選擇Objective-C;而Break主要選擇在拋出異常還是捕獲異常的時候斷點。

    這裏還是以數組訪問越界的例子,演示異常斷點如何定位問題位置。當我們添加異常斷點後執行程序得到如下圖所示結果,相比image指令,異常斷點會直接跳轉至錯誤代碼位置並清晰指出異常觸發原因。

    - 線程斷點\ 線程斷點適用在調試多線程代碼的時候,一段代碼可能會被多個線程同時執行,設置方法為,首先在代碼處添加普通斷點,然後獲取輸入指令thread info 獲取線程id,然後在LLDB中輸入指令 breakpoint set -f filename -l line_number -t tid

    執行 breakpoint list 指令可以查看當前包含的所有斷點,因為設置斷點是邏輯斷點,而一個邏輯斷點對應一個或多個位置斷點,當載入新的代碼時也會同時更新位置斷點,邏輯斷點和位置斷點使用“.”分割。

    當想禁用或者刪除斷點時可以執行指令breakpoint disable/delete breakpoint_number

    - Watch斷點\ 常用於跟蹤某個變量發生的變化,當發生變化時觸發斷點。設置方法為,在變量第一次出現的位置新建普通斷點,然後在Variables View窗口選中需要監控的變量並右鍵,選擇Watch "_text",這時便可以在斷點導航器中查看到新建的Watch斷點。

    當被監控變量發生變化時便會觸發斷點,並在LLDB終端顯示,幷包含新舊值對比。

    繼續執行,可以看到控制枱輸出後續變化的監測信息。

    這時的信息是十六進制,若需要查看其具體含義,則可以使用po指令。

    在實際開發中,可能需要使用逆向工程的方式來觀測某個變量的變化,這個時候我們可以使用LLDB的命令行來添加Watch 斷點,好處在於可以直接使用變量地址進行監測,指令格式為 watchpoint set expression address ,刪除Watch斷點可以使用 watchpoint delete <cmd-options> [<watchpt-id | watchpt-id-list]的可選參數為 -f (force)。

3.4 調試例子

這裏我們嘗試運行程序,使用 Command + R 指令,但程序構建失敗,報錯如下,我們找到和我們密切相關的部分,這裏我們優先定位紅框標出的部分。

使用image lookup -a 0x00000001049707ae 指令查找出錯位置,信息如下。

感覺對我們幫助不大,這裏main函數是主入口,也是項目初始化生成的部分。

我們嘗試使用全局異常斷點獲得其他的異常信息。這裏新建異常斷點(Exception Breakpoint),參數使用默認值。再次運行程序得到以下信息,點擊綠色斷點信息將其展開,可以看到提示“UITableView dataSource returned a nil cell for row at index path:...",我們的TableView返回了一個空值導致了程序崩潰。這裏我們可以自己定位代碼中哪裏有使用過TableView也可以直接使用靜態代碼分析。

因為程序構建就崩潰,我們優先嚐試使用靜態代碼分析檢查錯誤。這裏使用 Command + Shift + B 指令,得到如下提示,也幫我們定位到了代碼問題所在,在調用[tableView: cellForRowAtIndexPath:]方法時返回了空值。所以我們需要修改代碼邏輯,在最後補充默認返回值但不能為空,增加一個else語句。

代碼修改如下所示,執行程序,成功運行,但是最後TableView並沒有顯示我們想要的結果。

這裏應該是存在內在邏輯錯誤,所以需要藉助斷點排查問題。這裏我們分別在 [tableView:numberOfRowsInsection:] 和 [tableView:cellForRowsAtIndexPath:] 方法的返回處添加普通斷點,因為這樣我們方便查看最後的返回值,斷點所在行的代碼是還未執行的。除此之外我們還在 [tableView:cellForRowsAtIndexPath:] 方法的if語句開頭處添加斷點,方便我們查看indexPath變量的值。

我們先逐個排查返回值是否存在異常,這裏添加斷點後,程序便會停留在斷點處,我們再使用po指令輸出我們想查看的值。這裏我們先查看cellNum的值為2,並無異常,我們再查看indexPath變量中的item屬性發現為空,則需要在程序中添加兜底邏輯,讓最後的cell返回非空值,這裏在第二張圖中也可以印證左邊variable窗口cell最後依然是空值。圖中為後續添加的else語句。

點擊繼續按鈕,讓斷點往下執行,再次輸出indexPath的item屬性,並無異常。執行完斷點後便可以看到tableview顯示了一行結果。

\ 但是上面一行還是沒有信息填充肯定是不符合預期的,繼續調試。

這裏我們可以聚焦調試導航欄,查看程序調用的堆棧信息,發現這裏它存在一個預生成邏輯,最初會生成一個全局的cell方便後面複用(TableView中的cell需要註冊後使用,這樣可以在原來cell的基礎上修改少量顯示信息便能展示新的cell,實現複用,極大提升性能)。

打印第一次返回的cell的信息發現返回的text屬性都為空。

當繼續執行斷點,再次打印cell相關的text信息,可以得到以下結果。

結合代碼可以知道,當indexPath.item值為1時,這裏會使用懶加載生成cell然後賦值。

由此我們可以得到一個結論,第一次返回的是TableView創建的全局cell但是text屬性值為空,這裏我們需要給第一次創建的全局cell給一個合理的初值,然後執行代碼,問題解決。

4 Log的使用

  雖然斷點功能強大,但這裏還是需要補充一樣不可或缺的工具——Log。有的時候調試的時候bug無法復現,一旦關閉調試,bug便會出現。這一般是出現在多線程中,調試模式影響了多線程的執行。所以我們需要藉助Log去檢查程序執行順序是否符合預期。這裏我們需要藉助pch(Precompiled header)文件設定我們的打印操作如何執行,至於pch文件是什麼以及有何作用可以看這篇文章,Xcode6之後需要手動添加pch文件,添加方法看這裏,完成第一步即可,這裏着重介紹如何在pch文件中書寫Log規則以及在項目中引入pch文件。

當我們在項目中生成了後綴為.pch的文件後,雙擊打開可以看到以下描述。

我們在#define和#endif中間添加需要的宏,這裏我們添加以下內容。第11行的DEBUG標識可以幫助我們區分Debug和Release狀態,這樣在軟件發佈時日誌信息便不會再打印。第12行中的KSELog為打印的方法名,可以自己定義。這裏增加了文件名以及行數的信息打印,方便後續查看Log日誌定位問題。

```

ifndef KSELog_pch

define KSELog_pch

ifdef DEBUG

#define KSELog(...) \ NSLog(@"%@第%d行:%@\n---------------------------",[[NSString stringWithFormat:@"%s",FILE] componentsSeparatedByString:@"/"][[[NSString stringWithFormat:@"%s",FILE] componentsSeparatedByString:@"/"].count-1], LINE, [NSString stringWithFormat:VA_ARGS]);

else

define KSELog(...)

endif

endif / KSELog_pch /

```

之後我們需要在項目中引入pch文件。首先我們需要打開項目的target找到Apple Clang - Language,這裏可以使用過濾器快速定位。

然後我們需要將 Precompile Prefix Header 屬性置為Yes,除此之外需要在Prefix Header部分添加pch文件的絕對路徑,這裏可以藉助Finder和Terminal快速獲取文件路徑,將Finder中的文件拖入Terminal便可以得到打印的路徑信息。這裏需要雙擊箭頭指向的部分,進入編輯模式,將複製的路徑粘貼進去即可。

效果查看。

5 Instruments

  Instruments是功能強大的性能檢測和代碼追蹤工具,常用於內存泄露檢測,性能分析,支持多線程調試,可以將錄製的圖形界面操作和Instruments保存為模板,供以後訪問使用。因為Instruments提供的功能較多,無法一一列舉,這裏主要介紹Leaks模版的使用。首先本文會簡要説明Instruments提供了哪些主要功能,然後聚焦於Leaks模版的使用。打開方式為,點擊頂部工具欄的Product,然後選擇Profile即可。

5.1 主要功能(基於Xcode 13.4)

常用模板:

  • Allocations:用來檢查內存分配,跟蹤過程的匿名虛擬內存和堆的對象提供類名和可選保留/釋放歷史
  • Leaks:一般用來查看內存使用情況,檢查泄漏的內存,並提供了所有活動的分配和泄漏模塊的類對象分配統計信息以及內存地址歷史記錄
  • Time Profiler:分析代碼的執行時間,執行對系統的CPU上運行的進程低負載時間為基礎採樣
  • Zombies:檢查是否訪問了殭屍對象

  • Blank:創建一個空的模板,可以從Library庫中添加其他模板
  • Activity Monitor:顯示器處理的CPU、內存和網絡使用情況統計
  • Animation Hitches:動畫監視,此模板通過時間分析來度量應用程序圖形性能以及進程的CPU使用情況
  • App Launch:啟動問題,可以用於查看App的啟動過程,從而可以針對性的對啟動速度進行優化
  • Core Data:監測讀取、緩存未命中、保存等操作,能直觀顯示是否保存次數遠超實際需要
  • CPU Counters:幫助開發者定位CPU佔用高的線程和函數,來優化App的CPU性能問題
  • CPU Profiler:週期性的CPU負載分析器,使用硬件性能監測中斷來提供可靠的性能監測,無論代碼運行在高速還是節能的CPU上
  • File Activity:文件活動,此模板監視文件和目錄活動,包括文件打開/關閉調用、文件權限修改、目錄創建、文件移動等
  • Game Performance:方便開發者瞭解對遊戲運行表現和幀率影響至關重要部分代碼的運行情況
  • Logging:統一的日誌管理系統,方便將日誌和線索可視化。引入歸檔日誌文件時的默認模版
  • Metal System Trace:金屬系統跟蹤,Metal System Trace通過提供來自應用程序、驅動程序和GPU層的跟蹤信息,介紹了iOS、tvOS和macOS Metal應用程序的性能
  • Network:分析應用程序如何使用TCP / IP和UDP / IP連接使用連接儀器。就是檢查手機網速的。(這個最好是真機)
  • SceneKit:概述應用程序對SceneKit的使用。確定進入每個幀的工作類型,例如動畫、物理、場景選擇和渲染
  • System Trace:系統跟蹤,操作系統中發生的事情的一個全面的觀點。瞭解如何跨cpu調度線程,瞭解系統調用和虛擬內存錯誤如何影響應用程序的性能

5.2 Leaks模版

如上所述,Leaks模板主要用來檢測內存分配情況,查探是否發生了內存泄漏,一般和Allocations結合使用。這裏以Xcode 13.4版本為例,演示Leaks模板的使用。首先我們在示例代碼中增加一段會引發內存泄漏的代碼,如下所示。我們簡單定義一個結構體,然後模擬申請內存但不釋放的情況,因為ARC只針對NSObject,所以這裏的內存空間是需要手動回收的。

我們按照先前提示步驟打開Profile界面,選擇Leaks,然後進入分析界面。注意此時代碼是沒有執行的,需要點擊分析界面左上角的紅色同心圓,開始程序的檢查分析。

分析界面如下所示,上面一欄Allocations是分析代碼的內存分配情況,下面一欄Leaks是檢測是否發生了內存泄漏,綠色標識指明暫未發現內存泄漏,紅色標識表明檢測到發生了內存泄漏。這裏我們以C語言中的結構體申請但不釋放為例,演示Leaks模板的使用。

示例代碼如下,重寫KSEViewController的Init方法,添加內存申請代碼。邏輯為在主界面點擊跳轉到KSEViewController對應的界面,然後返回,因為結構體Book為自定義結構,需要手動釋放,若返回到主界面未釋放內存則會被檢測到內存泄漏。

注意使用Instruments模版需要先在Xcode中編譯運行,然後按步驟打開模版檢測。這裏可以看到在Leaks一欄出現了紅色標識,我們點擊右下方的Stack Trace中的堆棧記錄,跳轉到對應含有內存泄漏風險的代碼處,修復Bug。

6 調試補充

6.1 view hierarchy的使用

在進行UI界面開發時,常常需要觀察頁面層級關係以及位置關係,這時就可以使用Xcode中自帶的Debug View Hierarchy,打開方法為運行項目程序後點擊下方的Debug View Hierarchy,等待程序加載完畢後,就可以看到。

  • 1——Adjust the spacing between the views:調整視圖間的間隙,一般是由2D視圖轉變為3D視圖;
  • 2——Show/Hide Clipped Content:展示或者隱藏當前界面沒有被展示的內容;
  • 3——Show/Hide Constraints:展示或者隱藏界面約束;
  • 4——Adjust View Mode:調整視圖模式,可選僅內容展示、僅線框展示以及內容和線框同時展示;
  • 5——Change canvas background color:改變畫布的背景顏色,深色模式和淺色模式,支持真機;
  • 6——Orient to 2D/3D:調整視圖為2D或者3D模式
  • 7——Adjust the visible range views:調整可見的視圖範圍;

6.2 真機截圖

實際開發中,有的時候需要給產品或者測試人員展示界面效果,這個時候就可以使用Xcode中的截圖功能,當我們選擇頂部導航欄的 Debug 選項,然後找到 View Debugging ,最後點擊 Take Screenshot of xxx的iPhone ,這樣就能獲得存放在桌面位置的真機截圖。

總結

本文主要結合了個人的經驗和查閲的資料,希望能對iOS的開發者們有所幫助,在這裏找到需要的Debug技巧。如果內容有所不足,還請批評指正,有任何問題,也希望多多留言,共同探討進步!

hi, 我是快手電商的小毅

快手電商無線技術團隊正在招賢納士🎉🎉🎉! 我們是公司的核心業務線, 這裏雲集了各路高手, 也充滿了機會與挑戰. 伴隨着業務的高速發展, 團隊也在快速擴張. 歡迎各位高手加入我們, 一起創造世界級的電商產品~

熱招崗位: Android/iOS 高級開發, Android/iOS 專家, Java 架構師, 產品經理(電商背景), 測試開發... 大量 HC 等你來呦~

內部推薦請發簡歷至 >>>我們的郵箱: [email protected] <<<, 備註我的花名成功率更高哦~ 😘