你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 怎樣創建一個Xcode插件(Part 2)

怎樣創建一個Xcode插件(Part 2)

編輯:IOS開發基礎

原文:How To Create an Xcode Plugin: Part 2/3

原作者:Derek Selander

譯者:@yohunl

譯者注:原文使用的是xcode6.3.2,我翻譯的時候,使用的是xcode7.2.1,經過驗證,本部分中說的依然是有效的.在文中你可以學習到一系列的技能,非常值得一看.這些技能不單單只是用來創建插件,對你平時的調試等,也有非常大的幫助.

歡迎你來到創建xcode插件教程的第二部分.在第一部分中,你已經了解了怎麼通過NSNotification來窺探xcode的內部類,並且代碼注入了其私有類DVTBezelAlertPanel,並且,你還添加了一個NSMenuItem菜單到xcode的菜單欄裡,通過它來持久化是否開啟我們自定義的插件Rayrolling的功能

在這個教程中,你會在第一部分中創建的Rayrolling的基礎上繼續向前,如果你並沒有在這之前閱讀第一部分或者你想從本處開始,你可以下載第一部分完成的demo.本章中,你將會通過深入了解一些可用的工具來進一步探索Xcode,利用你將會學到的新知識,來修改Xcode的標題欄,使標題欄改為展示Ray的某首歌的歌詞。

createxcodeplugin2_1.png

開始啦

打開Xcode和Terminal(終端),並且將它倆都平鋪在桌面上,這樣你可以同時看到它們,方便後續的操作.(yohunl注:後文將會一面在Xcode上點擊,一面觀察終端控制台的輸出,一旦發現我們需要的輸出,就要立馬停止終端控制台,所以,平鋪在桌面上才便於你操作,否則,你就忙不過來啦)

createxcodeplugin2_2.png

因為從上一章,你已經學會了從xcode運行一個xcode實例來創建插件和調試插件,所以我們換一種新的方式:你將會直接通過終端命令來使用LLDB來探索Xcode,你不用再像第一部分那樣從一個Xcode啟動另一個調試用的Xcode實例來探索Xcode是怎樣工作的了.

LLDB的BFF和DTrace

DTrace是最好的探索Xcode的工具之一,它是一款相當優秀的調試工具,它是Instruments的基石.只要你了解怎麼使用它,你將會發現它是非常有用的工具.

首先,為了對其有個了解,我們需要一個使用DTrace的demo,就如同我們學習一門新語言時候,總是會通過一個Hello Worlddemo一樣.那麼開始吧… 創建一個腳本,它是用來保持所有以IDE開頭的類的運行計數,並在每次你調用該特定類方法或者實例方法時,增加計數.在你退出腳本時候,DTrace將會轉儲這些數據.

運行Xcode,然後,在Terminal的一個新窗口中,輸入如下的腳本:

sudo dtrace -n 'objc$target:IDE*::entry { @[probemod] = count(); }'  -p `pgrep -xo Xcode`

雖然你鍵入命令後並不會看到任何的輸出,但是DTrace已經在後台默默的生成了所有方法調用的蹤跡(trace).回到Xcode,隨便嘗試一些操作.打開某些文件,隨便做一些點擊操作.回到Terminal,然後按Ctrl+C來結束腳本,腳本產生的內容將會輸出到Terminal上 (只有在你Ctrl + C終止腳本後,才能在Terminal上看到腳本輸出的內容)

createxcodeplugin2_3.png

相當酷吧:),其實使用Dtrace,你可以做到很多事,但是我們的教程不會覆蓋你想了解的DTrace的全部,相反的,我們提供一個快速的Dtrace語法的概述,它將會幫助你開始:

createxcodeplugin2_4.png

1.Probe Description(探頭說明,前序描述):由提供者(Provider),模塊(Module),功能(Function),名字(Name)組成,它們由冒號分隔.省略它們中的任何一個,將會使Probe Description包含所有的匹配項.你可以使用 * 和 ? 操作符來做模式匹配.

  • Provider:這個包含了一系列的類和方法,例如profile,fbt,或者io.在我們的教程中,主要使用objc來hookObjective-C的方法調用.

  • Module:在OC語言中,這部分指示的是你希望觀察的指定的類名

  • Function:指示你希望觀察的特定方法名

  • Name:雖然根據你的Provider的不同,有不同的name值可選,但是你在此只需要使用entry或者retrun,來匹配函數的開始或者結束.

你要了解,你可以使用$target關鍵字來匹配進程ID,也可以使用p或者c標志來指定target.

2.Predicate(謂詞部分): 可選的表達式,它是用來過濾哪些動作是滿足條件的.你可以把它當做一個if語句的條件部分.

3.Action(動作): 將要執行的操作.它可以只是簡單的輸出一些內容到控制台,也可以是執行一個高級功能.

就如同LLDB的命令image lookup -rn {Regex Query}一樣,你也可以使用l標志來轉儲(dump)特定進程中的類和方法.

為了演示這點,我們來看這樣一個簡單的例子,運行Safari,然後在終端輸入以下腳本

sudo dtrace -ln 'objc$target::-*ecret*:entry' -p `pgrep -xn Safari`

上述的DTrace腳本會打印出所有的名字中包含ecret字符串的實例方法.因為Probe Description部分 提供了Name參數entry,所有的方法都有entry和return,所以基本上忽略了所有的重復查詢請求.

注意:如果你想掌握更多的Dtarce知識,你可以參考Big Nerd的文章和 這本Dtrace書籍.如果並沒有理解所有的這篇快速介紹中介紹的內容,也不需要沮喪,因為Dtrace是一個相當復雜的工具.

現在你了解到Dtace的最基本的內容,我們可以開始使用它來尋找我們感興趣的NSViews了.因為Xcode含有大量的Views,你會發現,使用LLDB來試圖找出它們,讓你不堪重負.即使結合LLDB的條件斷點,調試一些應用程序都包含的共同東西,也是一個丑陋的過程.

幸運的是,使用Dtrace,將能帶給你對付此類問題的巨大的幫助.你將會使用DTrace來找出Xcode中組成標題欄的視圖(NSViews).(⊙o⊙)…….那麼該怎麼做呢?

這是一種方法:當鼠標停止在,或者點擊一個NSView,hitTest:方法將會被觸發,這個方法將會返回在這個鼠標點下最深的子視圖.你可以使用DTrace來順著hitTest:方法來確定視圖,那個你應該進一步探索其其父視圖和子視圖的視圖.

在終端中輸入以下腳本:

sudo dtrace -qn 'objc$target:NSView:-hitTest?:return /arg1 != 0/ { printf("UIView: 0x%x\n", arg1);  }' -p `pgrep -xo Xcode`

一旦這段腳本運行,請確定你的Xcode是第一響應者(通過在Xcode上任何地方點擊一下即可成為第一響應者).在Xcode的窗口上移動你的鼠標指針,當你停止移動鼠標指針的時候,Dtrace將會輸出一個內存地址多次,這是因為 hitTest: 被在視圖層次上的多個視圖調用.

定位到Xcode的標題欄,點擊標題欄.選擇出現在終端中最近的地址,拷貝到你的粘貼板中

打開一個新的終端窗口標簽,運行LLDB,並將其附著到Xcode上,然後輸出從DTrace中拷貝出的地址,像以下這樣操作:

> lldb
(lldb) pro attach -n Xcode
... 
(lldb) po {上一步拷貝到粘貼板中的對象地址,例如是0x7fcc2687cd40,則該命令是 po 0x7fcc2687cd40}
...

其中,pro 是 progress的縮寫,LLDB使用的是前序匹配原則,在不引起歧義的情況下 process,proc,proce都是可以的.在下面你還會看到 pro at -n Xcode這種寫法,都是因為這個原則,都是等效的. pro attach -n Xcode ,使用attach命令直接在LLDB中把一個正在運行的進程連接到LLDB中,以便於進行動態調試.也就是說這個命令直接可以將LLDB添加到任意的其它不是由它自己創建的進程中!!

你將會看到和以下相似的輸出:

Dtrace_hitTest_2.gif

注意:現在,Xcode被我們通過終端輸入的LLDB命令所暫停了.你可以在任何時候通過輸入process interrupt或者其縮寫pro i來暫停Xcode,從而能夠開始調試Xcode.另外,你也可以通過在任何時候輸入continue或者縮寫c來恢復Xcode的運行.請確定你總是了解附著於Xcode上的LLDB的狀態,你可以認為,當Xcode被你的調試LLDB所暫停時候,它是不能夠相應你的單擊等操作的(如同你在Xcode中打了斷點,調試的時候進入到斷點一樣)

取決於你在Xcode中點擊的地方,你可能會命中一系列的視圖.探索這些內存地址的父視圖(通過在lldb中 po [對象地址 supview])或者子視圖(po [對象地址 subvies]),直到你得到了視圖 IDEActivityView.

一旦你找到了IDEActivityView,怎樣確定這個視圖就是你所希望找尋的視圖呢?

通過在LLDB中輸入以下命令:

(lldb) po [{找到的IDEActivityView實例的地址} setHidden:YES]  在我的機子上是  po [0x7fcc2686b3c0 setHidden:YES]
...這裡的點表示我們省略了lldb的某些不重要的輸出.
(lldb) c
...

如果是我們所期望的那個標題視圖,那麼現在Xcode的標題欄就被隱藏了,這就能證實它的確是我們所期望的標題欄的視圖啦.

通過如下命令來重新顯示它:

(lldb) pro i
(lldb) po [{找到的IDEActivityView實例的地址} setHidden:NO]  比如在我的機子上是  po [0x7fcc2686b3c0 setHidden:NO]
(lldb) c

以下這個圖是上述在LLDB中輸入命令的gif動畫,供你參考:

LLDB_setHidden.gif

按照以往的經驗,你知道,當你Build或者Stop一個Xcode工程的時候,標題欄視圖的內容將會變化.你可以用DTrace來觀察相關的函數.IDEActivity前綴是一個獨特的命名約定,你可以觀察所有的以IDEActivity開頭的類,以此來觀察在這幕後的所有相聯系的事情.

回到終端,通過Control + C來停止DTrace,然後輸入以下命令到終端中:

sudo dtrace -qn 'objc$target:IDEActivity*::entry  { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probemod] = count(); }' -p `pgrep -xn Xcode`

這段腳本將會輸出所有類名前綴是IDEActivity的所有方法調用.一旦你停止這個腳本命令(Control+C),它還會輸出指定類方法的被調用次數.

yohunl備注: 當你輸入這個命令的時候,可能會遇到譯者遇到的這種情況

yohunldeMacBook-Pro:~ yohunl$ sudo dtrace -qn 'objc$target:IDEActivity*::entry  { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probemod] = count(); }' -p `pgrep -xn Xcode`

dtrace: invalid probe specifier objc$target:IDEActivity*::entry  { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probemod] = count(); }: probe description objc50360:IDEActivity*::entry does not match any probes

當遇到這種情況不要擔心,不要茫然,你只要完全退出Xcode,再重新啟動Xcode,再輸入這個命令就好了.

開始這個DTrace命令,回到Xcode中,隨便打開一個工程,編譯並運行,然後再停止.注意觀察Xcode的標題欄的內容變化,然後停止Dtrace命令,查看結果:

Dtrace_IDEActivity.gif

仔細分析上面輸出的信息.IDEActivityView和其它類之間是怎麼運轉的都在控制台的輸出信息中,但是也有大量的其它信息夾雜在其中(控制台的輸出信息中),不是麼?

幸運的是,你可以有選擇的限制展示給你的信息量.通過浏覽相關的類,來確定是否有值得你進一步選擇性探索的內容….,也許 IDEActivityReport* 就是一個不錯的候選類,因為以它開頭的類看起來就是有聯系的(在我們上述的操作中,最後在DTrace看到的很多都是它,所以我們有理由覺得它就是有聯系的).

修改Dtrace腳本,使他看起來如下:

sudo dtrace -qn 'objc$target:IDEActivityReport*::entry  { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probemod] = count(); }' -p `pgrep -xn Xcode`

再運行,停止Xcode的動作的同時,密切關注控制台的輸出信息.一旦停止Xcode,立馬Control+C停止掉Dtrace的腳本,是否出現了一些類,看起來是值得我們進一步去探索的呢?

IDEActivityReportStringSegment看起來比較值得探索.縮小我們的腳本的搜索范圍,聚焦到這個類上來,注意以下命令的變化,注意Probe的Function部分的變化(我們上面分析了Dtrace命令的組成,其中Probe部分有個Function組成部分)

sudo dtrace -qn 'objc$target:IDEActivityReportStringSegment::entry  { printf("%c[%s %s]\n", probefunc[0], probemod, (string)&probefunc[1]); @num[probefunc] = count(); }' -p `pgrep -xn Xcode`

通過編譯,運行,停止Xcode工程;再一次停止Dtrace命令,觀察IDEActivityReportStringSegment類的實例的方法各自被調用的次數(當Control+C停止命令後,在控制台會輸出各個方法的調用次數).看起來,initWithString:priority:frontSeparator:backSeparator: 和 initWithString:priority:值得進一步分析!

打開一個新的LLDB會話(打開一個終端標簽頁面,lldb進入LLDB),然後再運行如下命令:

(lldb) pro attach -n Xcode
(lldb) rb 'IDEActivityReportStringSegment\ initWithString'
Breakpoint 9: 2 locations.
(lldb) br command add
Enter your debugger command(s).  Type 'DONE' to end.
> po NSLog(@"customidentifier %s %@", $rsi, $rdx) 
> c 
> DONE
(lldb) c

在上面這段命令中,你添加了自定義的命令,當任何屬於IDEActivityReportStringSegment類的以initWithString方法開頭的方法被調用時,就會執行添加的自定義命令.它輸出IDEActivityReportStringSegment實例的方法的 Selector 和 self (還記得教程一中提到的各個寄存器中存放的內容了吧)到控制台.

另外,使用了customidentifier 來標識 NSLog的輸出信息,這樣你可以更容易的從控制台的輸出中容易的定位到這些Log.

回到終端,新建一個終端tab(使用快捷鍵 command + t ),建立一個customidentifier的grep尾搜索:

tail -f /var/log/system.log | grep customidentifier

譯者注,這句話輸入後,並沒有輸出,當你切換到Xcode中,編譯,運行,就產生輸出了

編譯,運行Xcode,以便讓Xcode產生IDEActivityReportStringSegment變化.下面的gif動畫,顯示了上面所說的所有你輸入到LLDB中的命令,看起來如下:

36.gif

比較控制台的輸出和Xcode的標題欄的輸出,你可以明確的得到:它就是你要尋找的內容!!

到使用Swizzle的時候了

創建一個Objective-C的類NSObject的category,命名為Rayrolling IDEActivityReportStringSegment

添加下面的代碼到建立的文件 NSObject+Rayrolling_IDEActivityReportStringSegment.m:中:

#import "NSObject+Rayrolling_IDEActivityReportStringSegment.h"
#import "NSObject+MethodSwizzler.h"
#import "Rayrolling.h"

@interface NSObject ()

// 1
- (id)initWithString:(id)arg1 priority:(double)arg2 frontSeparator:(id)arg3 backSeparator:(id)arg4;
@end

@implementation NSObject (Rayrolling_IDEActivityReportStringSegment)

// 2
+ (void)load
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    [NSClassFromString(@"IDEActivityReportStringSegment") swizzleWithOriginalSelector:@selector(initWithString:priority:frontSeparator:backSeparator:) swizzledSelector:@selector(Rayrolling_initWithString:priority:frontSeparator:backSeparator:) isClassMethod:NO];
  });
}

- (id)Rayrolling_initWithString:(NSString *)string priority:(double)priority frontSeparator:(id)frontSeparator backSeparator:(id)backSeparator
{

  // 3
  static NSArray *lyrics;

  // 4
  static NSInteger index = 0;
  static dispatch_once_t onceToken;

  // 5
  dispatch_once(&onceToken, ^{
    lyrics = @[@"Never gonna live you up.",
               @"Never gonna set you down."];
 

  });

  // 6
  index = index >= lyrics.count -1 ? 0 : index + 1;

  // 7
  if  ([Rayrolling isEnabled]) {
    return [self Rayrolling_initWithString:lyrics[index] priority:priority frontSeparator:frontSeparator backSeparator:backSeparator];
  }
  return [self Rayrolling_initWithString:string priority:priority frontSeparator:frontSeparator backSeparator:backSeparator];
}

@end

下面是對上面代碼的一些解釋:

  1. 你需要前向聲明私有類的初始化方法initWithString:priority:frontSeparator:backSeparator:,否則編譯器直接就讓你通過不了.

  2. load是通常被用來初始化添加swizzle代碼的地方.

  3. 因為你使用一個category來交換方法,在category中建立實例變量是稍微有些復雜的.你可以通過associated objects給一個已存在的類添加實例變量,但這個是你自己應該掌握的內容.在這裡,我們直接使用一個static NSArray來保持一個對象繼續存在,即使它所在的方法已經執行完畢了.

  4. 您可以使用相同的static伎倆,使得index也如此。

  5. 使用dispatch_once初始化歌詞NSArray

  6. 遞增index,如果越界了,就重置它

  7. 檢測插件是否是可用狀態,如果是,那就更改string參數,否則,使用默認的參數

正如你所看到的,使用正確的工具可以幫助你快速定位你想要的東西.然而,要達到這個目的,卻有不止一種方法 - 你將會發現,通過堆(heap)分析照樣可以定位到你所感興趣的內容

通過堆(heap)來重新尋找IDEActivityView

在本節中,你將會重新搜尋Xcode的IDEActivityView和IDEActivityReportStringSegment,你將會使用一種略微不同的方式來達到這一目的.

到目前為止,你都是通過自上而下的方式來進行定位的:你先找到一個可能的class,然後以某種方式在內存中尋找它的一個實例對象,再查找它的成員方法和屬性.其實,以相反的方向來進行,似乎也是可行的,就是沿著參考鏈從下而上去尋找目標對象,直到找到它.

很幸運,Xcode中附帶了一些Python腳本,讓你能夠做到這點!

結束掉終端中所有的已存在的LLDB和Dtrace的會話,然後重啟Xcode.開始一個新的Xcode和LLDB會話,再一次執行LLDB附著到Xcode上的命令,然後,添加下面的命令腳本:

(lldb) pro at -n Xcode 
(lldb) command script import lldb.macosx.heap
"malloc_info", "ptr_refs", "cstr_refs", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.

這個腳本加載了一系列非常有用的方法到LLDB進程中.

  • malloc_info: 展示當前堆得對象的詳細信息.尤其是結合MallocStackLogging環境變量的時候及其有用!

  • ptr_refs: 給它一個堆空間中的一個內存地址,它可以找到指向這個內存地址的其它對象.

  • cstr_refs: 參數類型是char * ,查找它在內存中出現的地方.

  • objc_refs: 找到內存中指定的類的實例

你可能想先了解了解最容易的objc_refs.

在LLDB中,通過以下腳本來找尋所有的IDEActivityView類的實例變量:

(lldb) objc_refs -o IDEActivityView
Searching for all instances of classes or subclasses of "IDEActivityView" (isa=0x10fffe650)
0x00007faaee9cbf30: malloc(   304) -> 0x7faaee9cbf30 IDEActivityView.DVTLayerHostingView.NSView.NSResponder.NSObject.isa
[IDEActivityView: 0x7faaee9cbf30](因識別問題,這裡將尖括號替換為方括號)

這個腳本輸出了內存中所有的IDEActivityView類的實例.其中的 -o 參數表示輸出對象,也就是會調用對象的 description 方法

通過這個簡單地額腳本,你就可以在內存中找屬於指定類的的實例對象.這招也適用於繼承於該對象的子類.

yohunl備注:很遺憾,在我電腦上,這句怎麼都查詢不到

(lldb) objc_refs -o IDEActivityView
error: libarclite_macosx.a(arclite.o) failed to load objfile for /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_macosx.a
error: libarclite_macosx.a(arclite.o) failed to load objfile for /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_macosx.a
Searching for all instances of classes or subclasses of "IDEActivityView" (isa=0x10ada9328)

試了很多次,都沒能得到所期望的東西,但我相信這個方法是可以的

通過上一步 objc_refs發現的內存地址,你可以采用如下的腳本來展開一個IDEActivityView實例的成員變量:

(lldb) malloc_info -t 0x7faaee9cbf30
0x00007faaee9cbf30: malloc(   304) -> 0x7faaee9cbf30 IDEActivityView.DVTLayerHostingView.NSView.NSResponder.NSObject.isa (IDEActivityView) *addr = {
  DVTLayerHostingView = {
    NSView = {
      NSResponder = {
...

這還不是它能做到的全部!你甚至可以用它,通過指定的內存地址來獲得其它對象的引用:

(lldb) ptr_refs  0x7faaee9cbf30
0x00007faaeed9a308: malloc(    16) -> 0x7faaeed9a300 + 8     
0x00007faaeedb4148: malloc(   176) -> 0x7faaeedb4140 + 8      IDEActivityViewBackgroundButton.NSButton.NSControl.NSView.NSResponder._nextResponder
0x00007faaeedb4190: malloc(   176) -> 0x7faaeedb4140 + 80     IDEActivityViewBackgroundButton.NSButton.NSControl._aux.NSObject.isa
0x00007faaeedb4928: malloc(   160) -> 0x7faaeedb4920 + 8      NSView.NSResponder._nextResponder
...

注意:這些命令,如果結合 im loo -rn {classmethod regex} 方式來查詢,甚至可以幫助你快速的定位到,並且探查到在內存中存活的該類的所有實例.當然了,嘗試之前,請確定你的LLDB還是暫停狀態,否則的話,這些命令是不會起作用的.

你設置可以更進一步,有一個環境變量MallocStackLogging,它可以在你初始化任何一個對象的時候用於棧(stack)回溯.雖然很多引用都可以指向同一個對象,但是它可以指出來,哪個才是這個對象的”最終擁有者”. 這段還要再分析一下

由於上文的LLDB已經附著(attach)在xcode上,所以你要先殺掉Xcode進程,再通過帶上MallocStackLogging環境參數的LLDB來重啟它:

(lldb) pro kill 
Process 65196 exited with status = 9 (0x00000009)
(lldb) pro launch -v MallocStackLogging=1 -n
Process 67920 launched: '/Applications/Xcode.app/Contents/MacOS/Xcode' (x86_64)

注意:如果你比較懶,不斷的殺掉LLDB/Xcode會話,launchctl是一個更加方便的啟用環境變量MallocStackLogging的方式:

launchctl setenv MallocStackLogging 1

這個命令將通過launch創建的每一個進程的MallocStackLogging都設置成了true.所以你要記得,通過如下的命令來移除它們:

launchctl unsetenv MallocStackLogging

為了測試上面的內容,首先,確保你的Xcode的Edit菜單下的菜單項是 如下圖所示的 Enable Rayrolling :

Enable_RayRolling-700x378.png

現在,打開任何一個Xcode工程,保證源碼編輯窗口是打開的,編譯,運行工程.伴隨著工程的運行,你將會看到在編譯完成和運行完成的時候,IDEActivityView(假裝你不知道此時此刻,誰調用它)改變它的內容.

暫停執行這個Xcode工程,如果需要,使用下面的命令來重新加載LLDB的heap(堆)方法集:

(lldb) pro i 
(lldb) command script import lldb.macosx.heap
"malloc_info", "ptr_refs", "cstr_refs", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.

提醒:如果你對多次輸入這個命令感到厭煩,你可以去網上了解下怎麼在 ~/.lldbinit 方法中建立 command aliases (命令的別名,或者縮寫).這個在 ~/.lldbinit 文件的內容,在每次創建一個LLDB的會話實例的時候,都會被加載.

現在,使用cstr_ref方法,給它傳遞Xcode標題欄的字符串內容.舉個例子,如果這個工程的名字叫Rayrolling,那麼你的Xcode的標題欄上顯示的應該是諸如:”Running Rayrolling : Rayrolling:”此類的字符串:

(lldb) cstr_ref "Running Rayrolling : Rayrolling" 
0x0000000118cb21d0: malloc(    32) -> 0x118cb21d0
0x000000012079ae21: malloc(    48) -> 0x12079ae10 + 17     __NSCFString1 bytes after __NSCFString
0x00000001207a3bb3: malloc(    64) -> 0x1207a3b90 + 35     __NSCFString19 bytes after __NSCFString
0x00000001223834d1: malloc(    48) -> 0x1223834c0 + 17     __NSCFString1 bytes after __NSCFString
0x000000011f9126a1: malloc(    48) -> 0x11f912690 + 17     __NSCFString1 bytes after __NSCFString

正如你所看到的那樣,主要的參考信息一般都是 NSString類型的參考,多數情況下,你並不能從字符串本身得到更多的信息.所以你可能想了解這樣字符串是怎樣被創建的?因為有MallocStackLogging,做到這點,相當容易!

(lldb) cstr_ref "Running Rayrolling : Rayrolling"  -s

這個腳本輸出堆棧幀的信息,當這個char * 被創建的時候.

通過所有的堆棧信息去跟蹤實例變量,由於他們都是在內存中,內存地址的數量和其在內存中的相對位置都有可能不同,這些都取決於你和Xcode的互動方式的不同而不同.

通過閱讀分析堆棧的跟蹤信息,你將會獲得一組類,這些類就是負責Xcode中這方面的.

舉例來說,取決於你運行的這些LLDB查詢命令,你可能會看到這些class,如: IDEActivityScrollingTextLayer , IDERunOperation , IDEActivityView 等等諸如此類的class.

你還可以使用 ptr_ref 來作用於 cstr_ref 輸出的信息,來看那些對象保留了這個string對象的引用.

繁雜的隨機探索技巧

在Xcode中尋找正確的api是很復雜的,因為你是想在成千上萬的類和方法中尋找符合條件的幾個類而已.

使用LLDB的正則表達式,可以幫助你更好的找到它們.某些時候,最好的獲取信息的地方是代碼中使用的單例(Singleton),查看Xcode中存在的單例,看看是否值得進一步深入的單例.

當LLDB附著到Xcode,並且處於暫停狀態時候,輸入以下命令:

(lldb) i loo -rn "\+\[IDE.*\ shared"

這條命令將會查找以IDE開頭的類名,並且以”shared”開頭的類方法.這條命令等價於 target modules lookup -rn "\+\[IDE.*\ shared",-r是指後面的是正則表達式

另外一個吸引力的替代方案是:

(lldb) i loo -rn "\+\[[A-Za-z]*\ [A-Za-z]*\]"

它將會輸出所有的沒有參數的類方法.

正如你前面看到的,你可以很容易的找出你剛興趣的類,通過給一個名字給上面提到的Python腳本,就可以搜索到.另外還有一個流行的工具,可以用來搜索類和類方法,這個工具的名字叫class-dump,它可以在 這裡下載.它是LLDB命令im loo -rn {regex} 的另一種替代方式,因為它(Class-dump)可以輸出更加整潔,清晰的頭文件形式的輸出.

當然了,class-dump也有小缺點,它的缺點就是你必須限制你的搜索在指定的framework或者plugin中,而LLDB的image lookup 命令搜索的是整個的Xcode進程中的framework和plugin!也就是image lookup的搜索范圍更大,更廣.

下一步該做什麼?

這裡是第二部分最後完成的domo工程.

在這個教程中,你學習到了最基本的DTrace技能,並且學習和使用了一些你可以得到的高級LLDB方法.

如果你想了解更多的關於DTrace的知識,你可以閱讀另一篇obcj.io上的優秀文章,還有就是官方的 DTrace文檔

在本系列教程的第三部分,我們將再一次關注 匯編,你將會學到另一種靈巧的工具,它的名字叫 Cycript.

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