精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

怎樣創建一個Xcode插件

移動開發
原文使用的是xcode6.3.2,我翻譯的時候,使用的是xcode7.2.1,經過驗證,文章中說說的依然是有效的。在文中你可以學習到一系列的技能,非常值得一看。

[[163627]]

蘋果的"一個足以應付所有"策略使得它的產品越來越像一個難以下咽的藥丸。盡管蘋果已經將一些工作流帶給了iOS/OS X的開發者,我們仍然希望通過插件來使得Xcode更加順手!

雖然蘋果并沒有提供任何的官方文檔來指導我們如何創建一個xcode插件,但是開發者社區做了大量的工作開發了一些非常有用的工具,通過這些工具,可以用來幫助開發者。

從 自動完成圖片名的插件,到 清除緩存的插件 再到 使Xcode變成一個vim編輯器的插件,Xcode的插件社區已經拓展了我們的思維,我們可以讓Xcode變得更加智能。

在這個不算短的三部分教程中,你將創建一個Xcode的插件來娛樂你的同事,其特色莫過于顯示的內容并不是他所期望看到的!盡管這個插件是很輕量級的,你仍然可以學習到很多知識,例如,通過調試Xcode,怎樣找出你感興趣的元素并且修改它,怎樣將系統的功能函數替換為你自己的函數(通過swizzle技術)!

你將會使用 x86匯編知識代碼定技能以及LLDB調試技能來查閱未公開的私有framework,并且要探索這些私有framework中的私有API,還要使用 method swizzleing來進行代碼的注入。正因為有這么多內容,所以本教程的講解速度會很快。在繼續之前,請務必確定你已經掌握了相關的 iOS/OS X的開發。

使用Swift來開發插件,還是一個比較復雜的主題,并且Swift的調試工具依然比Objective-C要弱很多。就目前而言,這意味著插件開發的***選擇(本教程!)是Objective-C.

開始

為了慶祝 惡作劇你的同事日,你的Xcode插件將會Rayroll你的受害者。等等… 什么是Rayrolling?它是一個免費和無版權的Rayrolling瑞克搖擺-就是你看到的內容并不是你期望的內容,有點掛羊頭賣狗肉的意思。當你完成了這個系列,你的插件將會更改Xcode顯示的內容:

用Ray's的頭像來替換Xcode的某些警示框(例如, Build成功或者失敗的Xcode提示框)

替換Xcode的標題內容為Ray的熱門歌曲的一句歌詞,Never Gonna Live You Up

替換所有的Xcode中的搜索文檔內容為一個視頻 Rayroll'd video

在教程的***部分,我們將聚焦于尋找到負責展示"Build成功"警示框的那個類,并且將其圖片改為Ray的頭像這張圖片

安裝插件管理插件 Alcatraz

在開始之前,需要先安裝Alcatraz,它是Xcode插件管理工具。

典型的安裝Alcatraz的方式是通過命令行

  1. curl -fsSL https://raw.github.com/supermarin/Alcatraz/master/Scripts/install.sh | sh 

當這條命令結束后,重啟Xcode。你可能會看到一個提示 Alcatraz bundle的警示框;點擊 Load Bundle 繼續,以便xcode能夠加載Alcatraz插件,這樣這個Alcatraz插件才能起作用

注意:如果你一不小心點擊了"skip bundle",你可以通過命令行輸入以下命令來重新顯示它!

  1. defaults delete com.apple.dt.Xcode DVTPlugInManagerNonApplePlugIns-Xcode-7.2.1 

以上的Xcode 7.2.1是你機子上的Xcode版本號,如果你的不是7.2.1,改為你的對應的版本號就可以了。

你將會在Xcode的Window菜單下看到一個新的菜單項:Package Manager。創建一個xcode插件,需要你通過設置Build Settings來運行另一個新的Xcode的實例來加載才可以,這是一個枯燥和乏味的過程(如果想知道這個枯燥的過程,可以參考我的文章Xcode7 插件制作入門),幸好,已經有人替你完成了這件事情了,有人開發了一個Xcode的工程模板,可以讓你很方便的創建一個插件工程。

打開Alcatraz(Window->Package Manager)。在Alcatraz的搜索框中輸入Xcode Plugin。務必確保你選中了搜索框中的All和Templates兩個屬性。一旦你搜索到,單擊其左邊的Install來安裝它!!

如果你搜索不到,也沒關系,你可以前往 https://github.com/kattrali/Xcode-Plugin-Template自己下載下來的方式來加載它,具體安裝方式可以見工程的說明。

一旦Alcatraz下載完了Xcode Plugin插件,你就可以創建一個插件工程了(File->New -> Project…),選擇這個新的OS X ->Xcode Plugin ->Xcode Plugin模板,然后點擊下一步。

給工程取名字Rayrolling,組織的標識符為com。raywenderlich(這一步非常重要),選擇Objective-C作為代碼語言。。保存工程到任何一個你想放置的目錄中。

Hello World插件模板

編譯,然后運行這個Rayroll工程,你將會看到一個新的Xcode實例出現。這個Xcode實例在Edit菜單欄下多了一個菜單項Do Action:

選擇這個菜單項,將會出現一個模態的彈出框:

從Xcode5開始,插件都只能運行在特定版本的Xcode中。這也就意味著當新的Xcode更新安裝后,所有的第三方插件都將失效,除非你添加了該版本Xcode的UUID。如果部分模板沒有起作用,你也沒看到一個新的菜單項,可能的原因之一就是因為沒有對應版本的UUID,你需要添加對應該版本Xcode的支持。

為了添加UUID,首先是在命令行中運行以下命令

  1. defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID 

這條命令會輸出當前版本xcode的UUID。打開Rayroll工程的Info.plist文件。導航到DVTPlugInCompatibilityUUID,添加它

注意:通過本教程,你會運行和修改已經安裝了的插件。這將會改變Xcode的默認行為,當然,這也可能會導致Xcode crash!!如果你想禁止某個插件,你可以手動的通過終端去刪除它.

  1. cd ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins/ 
  2.     rm -r Rayrolling.xcplugin/ 

然后重啟一下Xcode

找到我們要修改的xcode的某項特性

最直接最有效的得到幕布后發生什么的方式是 通過注冊一個NSNotification observer 來監聽所有的Xcode事件。通過Xcode和監聽這些消息通知,你將會深入到一些內部類的內部。

打開Rayrollling.m,在類中添加如下的屬性

  1. @property (nonatomic,strong) NSMutableSet *notificationSet; 

這個NSMutableSet用來存儲所有的Xcode的控制臺打印出來的NSNotification的名字

下一步,在initWithBundle:中,if (self = [super init]) {之后,添加如下代碼

  1. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:nil object:nil]; 
  2.    
  3. self.notificationSet = [NSMutableSet new]; 

給name參數傳遞nil指示,偶們需要監聽xcode的所有NSNotification。

現在,實現handleNotification:方法:

  1. - (void)handleNotification:(NSNotification *)notification { 
  2.   if (![self.notificationSet containsObject:notification.name]) { 
  3.     NSLog(@"%@, %@", notification.name, [notification.object class]); 
  4.     [self.notificationSet addObject:notification.name]; 
  5.   } 

handleNotification:檢查獲取到的通知名稱是不是在notificationSet中,如果不是,則在控制臺打印出它的通知名和通知多對應的類。然后添加到notificationSet中。通過這種方式,你只會在控制臺看到每一種類型的通知一次

下一步,找到添加menu item的聲明,將其替換成下面的代碼

  1. NSMenuItem *actionMenuItem = [[NSMenuItem alloc] initWithTitle:@"Reset Logger" 
  2.  action:@selector(doMenuAction) keyEquivalent:@""]; 

這段代碼只是簡單的更改了NSMenuItem的標題,以便讓你知道,當你點擊它的時候,它將會重置存放NSNotification 的set對象。

***,替換doMenuAction的實現代碼為下面的

  1. - (void)doMenuAction { 
  2.   [self.notificationSet removeAllObjects]; 

這個菜單項將會重置所有存放在notificationSet屬性中的通知。這樣做的目的是讓你在控制臺中很容易的觀察到你感興趣的通知,而不至于被控制臺的重復消息所刷屏。讓你更加專注。

再一次編譯運行,請確認你分清了哪個是你工程的Xcode(也就是父Xcode),哪個是你debug出來的一個Xcode實例(子Xcode),為什么要分清楚呢?因為我們的每一次改變,當重新debug的時候,debug出來的Xcode中已經起作用了,而父Xcode,只有等到你重啟后,才能看到效果的。

在子Xcode中,隨便點擊點擊按鈕,打開一些窗口,瀏覽瀏覽程序,你會在父xcode的控制臺中看到消息的觸發。

查找和監測編譯的提示框

現在,你已經學會了基本的查看由Xcode本身引起的通知(NSNotification),現在你需要明確的找出來顯示編譯狀態的提示框所關聯的類是哪一個。

運行Xcode插件,在子Xcode中,打開任何一個工程,確保打開了Xcode設置中的bezel notifications,Succeeds和Fails中的bezel notifications都要打開。當然了,請再次確定你操作的是子Xcode實例!

通過你在Xcode的edit菜單中創建的 菜單項Reset Logger來重置notificationSet,然后運行你的子code(上面讓你在子Xcode中打開了任何一個工程,現在你在子Xcode中運行這個打開的工程)

當子Xcode的工程編譯結果出來后(或者成功,或者失敗都沒關系),關注父Xcode中控制臺輸出的信息。粗略的瀏覽一遍,看是否有能引起你關注的通知。你能夠發現一些值得你進一步關注的notifications么?下面的這些可能能給你一些幫助(原文中,以下的列表是隱藏的,你可以點擊展示它,作者鼓勵大家自己先找找看,如果找不到,再打開下面的這個提示,由于我使用的markdown編輯器的限制,做不到這點,所以此處直接放出來了)

下面的一些項值得你進一步關注:

  • NSWindowWillOrderOffScreenNotification, DVTBezelAlertPanel

  • NSWindowDidOrderOffScreenNotification, DVTBezelAlertPanel

  • NSWindowDidOrderOffScreenAndFinishAnimatingNotification, DVTBezelAlertPanel

你應該挑其中一個,并進一步探索它,看看是不是可以從中得到一些重要的信息。例如 NSWindowWillOrderOffScreenNotification是干什么的? 很好,你選擇了進一步探索NSWindowWillOrderOffScreenNotification。

回到父Xcode中的Rayrolled.m文件,定位到handleNotification:方法,添加一個斷點到方法中的***行,并且按照如下來設置這個斷點:

  1. 鼠標停在這個斷點,右擊這個斷點,選擇 Edit Breakpoint
  2. 在彈出的斷點編輯框中的condition輸入框中,添加[notification.name isEqualToString:@"NSWindowWillOrderOffScreenNotification"]
  3. 在Action部分,添加 po notification.object
  4. 如果父xcode已經處在運行狀態了,重新讓它運行debug,然后在生成的子xcode中,再編譯運行一個工程。父xcode中的斷點應該會停在NSWindowWillOrderOffScreenNotification通知。觀察控制臺輸出的-[notification object]的值DVTBezelAlertPanel,這也是***個值得你深入關注的諸多私有類中的一員

你現在知道了有一個類的名字叫DVTBezelAlertPanel,更重要的是,你知道內存中有一個這個類的實例。不過不幸的是,你找到不到任何關于這個類的頭文件能夠告訴你,這個類是否就是展示Xcode的警示框的。

實際上,還是可以獲取到這些信息的。盡管我們沒有關于這個類的頭文件,可是你有一個調試器連接到子Xcode,內存中的信息照樣可以告訴你關于這個類的相關信息,就如同你閱讀其頭文件一樣。

注意:在這個系列的教程中,LLDB的輸出通常是伴隨在標準的控制臺輸出中的。任何以(lldb)打頭的行,可以認為是輸入行,在此處你可以輸入一些命令。三個點…輸出在控制臺,表示控制臺來打印不及,忽略了其中某些。如果在控制臺顯示了太多的打印日志,可以直接按 ? + K來清楚當前的輸出,并且重新接受輸出

確保你父Xcode是調試狀態的,程序停在斷點處,輸入以下的lldb命令道父Xcode的lldb控制臺:

  1. (lldb) image lookup -rn DVTBezelAlertPanel 
  2. 17 matches found in /Applications/Xcode.app/Contents/SharedFrameworks/DVTKit.framework/Versions/A/DVTKit:  
  3. ...(這里的...是表示省略了上面那句命令的輸出內容,因為實在太多了) 

這句命令搜索任何的加載到xcode進程中的frameworks,libraries,plugins,查找名為DVTBezelAlertPanel的類的相關信息,然后輸出查找到的信息。觀察搜索結果列出的方法。你是否已經能夠找到一些方法可以用來關聯DVTBezelAlertPanel類和子Xocde中出現的編譯成功/失敗的警示框?下面我提供了一些方法的列表,這些可以幫助到你。(原文中,以下的列表是隱藏的,你可以點擊展示它,作者鼓勵大家自己先找找看,如果找不到,再打開下面的這個提示,由于我使用的Markdown編輯器的限制,做不到這點,所以此處直接放出來了).

有幫助的方法

以下列出的DVTBezelAlertPanel類的方法,值得你進一步探索:

  • initWithIcon:message:parentWindow:duration
  • initWithIcon:message:controlView:duration:
  • controlView

以上的兩個初始話方法中的任何一個,基本上就可以幫助你驗證是否關聯了類DVTBezelAlertPanel和出現的提示框中的內容了

注意:LLDB的image lookup命令只列出在內存中實現了的方法。當你使用這個查找某些類時候,它并不包含那些繼承于父類,但是子類并沒有重載的方法,也就是說它只列出自己實現了的方法。

確保依然停留在父Xcode的斷點出,在父類的LLDB控制臺中輸入以下命令來 檢測contentView

  1. (lldb) po [notification.object controlView] 
  2. [nil](此處尖括號替換為方括號) (這個是上句輸出的結果,yohunl注) 

控制臺輸出的是nil.(⊙o⊙)…,可能是因為這個contentView在這個時候還沒有被初始化吧。沒關系,我們嘗試下一個:initWithIcon:message:parentWindow:duration和initWithIcon:message:controlView:duration: ,因為你已經了解,內存中已經存在DVTBezelAlertPanel類的實例了,這意味著這兩個初始化方法,已經被調用過了。你需要給這兩個方法添加調試斷點,因為我們沒有它的實現文件,所以這里我們用LLDB控制臺來添加斷點。然后再次觸發這個類的初始化。

父Xcode依然停留在斷點出,輸入以下命令

  1. (lldb) rb 'DVTBezelAlertPanel\ initWithIcon:message:' 
  2. Breakpoint 12 locations. 
  3. (lldb) br l 
  4. ... 

yohunl備注:在xcode7.2.1中,顯示的是

  1.  (lldb) rb 'DVTBezelAlertPanel\ initWithIcon:message:' 
  2. Breakpoint 24 locations. 
  3. (lldb) br l 
  4. ........ 

這個正則表達式形式的斷點將會給上面的兩個初始化方法都添加一個斷點,這是因為兩個方法都有一個相同的起始字符,正則表達式將匹配它們兩個。別忘記上面正則表達式中空格前的\符號,還有就是用單引號'來包含整個表達式,這樣LLDB才知道怎么解析它

切換到子Xcode,重新編譯子工程(ctrl+B)。父Xcode將會***initWithIcon:message:parentWindow:duration斷點

如果沒有***斷點,檢查一下,是不是將斷點設在父Xcode中(假如你設在子xcode中,當然不起作用呀),是不是在子Xcode中編譯了一個工程。,因為找不到相應的源碼文件,Xcode將會斷點在方法的匯編代碼中。

現在,你在沒有源代碼的情況下,斷點進入了一個方法。你需要一個方式來打印出傳遞給該方法的參數。是時候讓我們談一談。。匯編了…:]

[[163630]]

匯編之旅

當你面對私有API的時候,你要做的往往是分析寄存器(registers ),而不是像在擁有源碼情況下的調試一樣使用調試符號(debug symbols )。了解寄存器(registers)在x86-64架構下的行為,將會給你提供很多的幫助.

盡管不是一篇必讀文章,這篇文章是一篇非常好的關于x86 Mach-0 匯編的文章。在本教程的第三部分,你將會通過方法的部分反匯編代碼去了解方法到底是做什么的。不過現在,你需要的只是簡單的了解。

以下的寄存器和其是怎樣工作的,值得你關注:

  • $rdi:這個寄存器代表傳遞給方法的參數self,這也是***個傳遞的參數。
  • $rsi:表示Selector,這是傳遞給的第二個參數
  • $rdx:傳遞給函數的第三個參數,也是我們看到的Objective-C的***個參數(因為self和Selector是隱含的參數)
  • $rcx:傳遞給函數的第四個參數,也是我們看到的Objective-C的第2個參數(因為self和Selector是隱含的參數)
  • $r8:傳遞給函數的第五個參數。如果需要傳遞更多的參數,$r9將會作為跟隨之后的第6個參數的棧幀
  • $rax:函數的返回值存放在此寄存器中。例如,當我們執行完方法-[aClass description],$rax將會存放aClass對象的描述NSString。

注意:以上描述的不是絕對的。在某些二進制中,會使用不同的寄存器來存放不同類型的參數,例如:doubles使用$xmm寄存器組。上面的只是作為一個快速的參考!

下面我們采用以下的方法,來將上述的理論運用到實踐中來

  1. @interface aClass : NSObject 
  2. - (NSString *)aMethodWithMessage:(NSString *)message; 
  3. @end 
  4.    
  5. @implementation aClass  
  6.    
  7. - (NSString *)aMethodWithMessage:(NSString *)message { 
  8.   return [NSString stringWithFormat:@"Hey the message is: %@", message];  
  9.    
  10. @end 

使用如下的代碼來執行它:

  1. aClass *aClassInstance = [[aClass alloc] init]; 
  2. [aClassInstance aMethodWithMessage:@"Hello World"]; 

編譯后,對方法aMethodWithMessage的調用將會由Runtime層準換為對objc_msgSend的調用,基本上類似于如下:

  1. objc_msgSend(aClassInstance, @selector(aMethodWithMessage:), @"Hello World"

aClass的方法aMethodWithMessage調用,會使得一些寄存機的內容被改變:

調用方法aMethodWithMessage前

  • $rdi: 存放aClass類型的一個實例變量
  • $rsi:存放SEL類型的aMethodWithMessage:,實際上它是一個chra * 類型的字符串(可以通過在lldb中 輸入 po (SEL)$rsi來驗證)
  • $rdx:包含傳遞給方法的message,此處,是一個字符串 @"Hello World" 

當調用方法結束后

  • $rax:存放方法執行后的返回值,在此處是一個NSString。在這個特定的例子中,存放的是字符串@"Hey the message is: Hello World"

x86寄存器

通過以上的內容,你已經有了一份寄存器指南了,是時候重新審視DVTBezelAlertPanel的初始化方法initWithIcon:message:parentWindow:duration:了。希望你的父Xcode的斷點還停留在此方法處。當然,如果不是,也沒關系,重新運行子Xcode,再次停留在父類的斷點initWithIcon:message:parentWindow:duration:處。記住,你是在尋找將類DVTBezelAlertPanel和顯示Xcode的編譯成功/失敗提示框之間的線索。

當程序斷點在initWithIcon:message:parentWindow:duration處,在LLDB控制臺輸入以下內容

  1. (lldb) re re 

這條命令是register read的縮寫,它是用來輸出當前你機器上可見的重要寄存器內容的命令。

運用你所學到的關于x86寄存器的知識,去查看哪個寄存器是用來存放message參數的和objc_msgSend方法的第四個參數。是否這個內容就是我們所希望得到的警示框的提示內容呢?(原文中,以下的列表是隱藏的,你可以點擊展示它,作者鼓勵大家自己先找找看,如果找不到,再打開下面的這個提示,由于我使用的markdown編輯器的限制,做不到這點,所以此處直接放出來了)

是的,你應該查看寄存器$rcx,你將會看到,它的內容就是message參數的內容,也就是顯示在xcode的編譯提示框中的提示信息

輸入以下命令來進一步深入了解:

  1. (lldb) po $rcx  
  2. Build Failed 

注意:Xcode輸出寄存器內容是采用默認的AT&T匯編格式的,在這種格式中,源操作符和目標操作符的位置是交換過的,意思是AT&T語法***個為源操作數,第二個為目的操作數,方向從左到右,這個同Intel的匯編格式是相反的。(譯者注:關于AT&T匯編,可以參考http://blog.csdn.net/bigloomy/article/details/6581754)

看起來這個就是我們要找的寄存器呀!

試著更改$rcx的內容為一個新的字符串,看看是不是警示框的內容改變了:

  1. (lldb) po [$rcx class
  2. __NSCFConstantString//我的xcode7.2.1上輸出顯示的是 __NSCFString 
  3.    
  4. (lldb) po id $a = @"Womp womp!";  
  5. (lldb) p/x $a  
  6. (id) $a = 0x000061800203faa0 //yohunl備注,這里是上一句p/x $a的輸出,在我的xcode7.2.1上,輸出的是(__NSCFString *) $a = 0x00006080026379c0 @"Womp womp!",在你的機子上輸出的地址也很可能是不一樣的,下一句中的地址要換成你機子上本處顯示的地址值 
  7. (lldb) re w $rcx 0x000061800203faa0 
  8. (lldb) c 

應用程序將恢復運行。注意觀察顯示的編譯成功/失敗的提示框的內容是不是變成了我們修改的字符串。你將會看到,它的確是變成了我們設置的新字符串,這也驗證了我們的假定- DVTBezelAlertPanel就是用來顯示這個提示信息的。

代碼注入(Injection)

你已經找到了你所需要的類,是時候,我們通過代碼注入來擴展DVTBezelAlertPanel的行為,在編譯提示框中展示lovely Rayrolling(人名)的頭像。

我們采用的是 metthod swizzling技術

你可能要swizzle來自很多不同的類的大量的方法,所以***建議是創建一個NSObject的category,在其中提供一個便捷的方法,來建立所有的swizzle邏輯。

在Xocde中,選擇File\New\File…,然后選擇 OS X\Source\Objective-C File,建立名稱為MethodSwizzler的文件,確保它的形式是NSObject的category。

打開NSObject+MethodSwizzler.m,將其替換成如下的代碼:

  1. #import "NSObject+MethodSwizzler.h" 
  2.    
  3. // 1 
  4. #import   
  5. @implementation NSObject (MethodSwizzler) 
  6.    
  7. + (void)swizzleWithOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL) swizzledSelector isClassMethod:(BOOL)isClassMethod  
  8.   Class cls = [self class]; 
  9.    
  10.   Method originalMethod; 
  11.   Method swizzledMethod; 
  12.    
  13.   // 2 
  14.   if (isClassMethod) { 
  15.     originalMethod = class_getClassMethod(cls, originalSelector); 
  16.     swizzledMethod = class_getClassMethod(cls, swizzledSelector); 
  17.   } else {  
  18.     originalMethod = class_getInstanceMethod(cls, originalSelector); 
  19.     swizzledMethod = class_getInstanceMethod(cls, swizzledSelector); 
  20.   } 
  21.    
  22.   // 3 
  23.   if (!originalMethod) {  
  24.     NSLog(@"Error: originalMethod is nil, did you spell it incorrectly? %@", originalMethod); 
  25.     return;  
  26.   } 
  27.    
  28.   // 4 
  29.   method_exchangeImplementations(originalMethod, swizzledMethod);  
  30. @end 

其中的關鍵代碼都加了序號,下面一一解釋:

  1. 這是使用method swizzle所需要的頭文件
  2. isClassMethod指示,這個方法是實例方法還是類方法
  3. 如果不借助于編譯器的方法提示,那很容易拼錯上述的方法。這段代碼是用來檢查的,確保你的拼寫是正確的
  4. 這個是關鍵函數,用來交換方法的實現的

在頭文件NSObject+MethodSwizzler。h中添加方法swizzleWithOriginalSelector:swizzledSelector:isClassMethod的聲明,如下所示:

  1. #import   
  2. @interface NSObject (MethodSwizzler) 
  3. + (void)swizzleWithOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL) swizzledSelector isClassMethod:(BOOL)isClassMethod; 
  4.    
  5. @end 

接下來,就可以完成實際的swizzle了。創建一個新的名為Rayrolling_DVTBezelAlertPanel的category,這個category同樣是NSObject的category。

替換創建的NSObject+Rayrolling_DVTBezelAlertPanel。m的代碼為如下代碼

  1. #import   
  2. // 2 
  3. @interface NSObject () 
  4.    
  5. // 3 
  6. - (id)initWithIcon:(id)arg1 message:(id)arg2 parentWindow:(id)arg3 duration:(double)arg4; 
  7. @end 
  8.    
  9. // 4 
  10. @implementation NSObject (Rayrolling_DVTBezelAlertPanel) 
  11.    
  12. // 5 
  13. + (void)load 
  14.   static dispatch_once_t onceToken; 
  15.    
  16.   // 6 
  17.   dispatch_once(&onceToken, ^{ 
  18.    
  19.     // 7 
  20.     [NSClassFromString(@"DVTBezelAlertPanel") swizzleWithOriginalSelector:@selector(initWithIcon:message:parentWindow:duration:) swizzledSelector:@selector(Rayrolling_initWithIcon:message:parentWindow:duration:) isClassMethod:NO]; 
  21.   }); 
  22.    
  23. // 8 
  24. - (id)Rayrolling_initWithIcon:(id)icon message:(id)message parentWindow:(id)window duration:(double)duration 
  25.   // 9 
  26.   NSLog(@"Swizzle success! %@", self); 
  27.    
  28.   // 10 
  29.   return [self Rayrolling_initWithIcon:icon message:message parentWindow:window duration:duration]; 
  30.    
  31. @end 

上面的代碼比較簡單,我們來分析:

  1. 引入用來swizzling的頭文件
  2. 前向聲明所有你打算使用到的方法。雖然這不是必須的,但是這個使得編譯器能夠智能感知完成你的代碼,另外,這也消除了編譯器提示的找不到方法聲明的警告
  3. 這是你實際上需要swizzle的方法
  4. 因為我們不想重新聲明一個私有類,替代的方式是聲明一個category。
  5. 這個方法是觸發代碼注入的地方。你應該將代碼注入都放到load中。這個load是唯一的一個"一對多關系"的方法,也就是說,多個category都有load方法,那么所有的category的load方法都能夠得到執行
  6. 因為load可能會被多次執行,所以,使用dispatch_once確保只執行一次
  7. swizzle前面聲明的方法為你自己的實現。當然了,其中使用了NSClassFromString來動態的獲取內存中的類!
  8. 這是你寫的用來取代原來的方法的方法,建議是它采用獨特的命令方式,這樣從名字立馬知道它是做什么的
  9. 輸出一下,確保swizzle成功了
  10. 因為你已經swizzle了原始的方法,那么你調用swizzled后的方法(此處是[self Rayrolling_initWithIcon:icon message:message parentWindow:window duration:duration];),它將會調用的是原來的方法。這意味著你在原來的方法執行之前或者之后,添加任何你想要的代碼,甚至是更改傳遞給原始方法的參數…當然了,這里你已經在這么做了

祝賀你,你已經成功的注入代碼到一個私有類的私有方法中了!編譯父xcode,然后在子xcode中編譯運行一個工程,查看父xcode的控制臺輸出,看是不是成功的wswizzled了。

  1. Swizzle success! [DVTBezelAlertPanel: 0x11e42d300](由于識別問題,此處將尖括號替換為方括號) 

接下來,你就可以替換編譯成功/失敗的提示框上的圖標為Rayrolling頭像啦。從這里下載頭像資源Crispy from here,然后添加到工程中來,確保選擇了 Copy Items if Needed。

現在,導航到方法Rayrolling_initWithIcon:message:parentWindow:duration,將其代碼改為:

  1. - (id)Rayrolling_initWithIcon:(id)arg1 message:(id)arg2 parentWindow:(id)arg3 duration:(double)arg4  
  2.   if (arg1) {   
  3.     NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.raywenderlich.Rayrolling"]; 
  4.     NSImage *newImage = [bundle imageForResource:@"IDEAlertBezel_Generic_Rayrolling.pdf"]; 
  5.     return [self Rayrolling_initWithIcon:newImage message:arg2 parentWindow:arg3 duration:arg4]; 
  6.   } 
  7.   return [self Rayrolling_initWithIcon:arg1 message:arg2 parentWindow:arg3 duration:arg4]; 

這個方法首先檢查是否一個圖片參數被傳遞給了原方法,然后將其替換成我們自定義的圖片。注意:此處你是使用[NSBundle bundleWithIdentifier:@"com。raywenderlich。Rayrolling"];來加載圖片的,這是因為xcode的MainBundle并不包含我們的資源。

重新編譯父Xcode,然后在子Xcode中編譯一個工程,你會看到

添加一個開關和持久化

設計這個plugin是用來娛樂用的,所以你肯定需要一個開關,讓它起作用或者不起作用。我們通過NSUserDefaults來持久化存放使它起作用或者不起作用的變量.

導航到Rayrolling.h ,添加如下代碼

  1. + (BOOL)isEnabled; 

在Rayrolling.m文件中添加

  1. + (BOOL)isEnabled { 
  2.   return [[NSUserDefaults standardUserDefaults] boolForKey:@"com.raywenderlich.Rayrolling.shouldbeEnable"]; 
  3.    
  4. + (void)setIsEnabled:(BOOL)shouldBeEnabled { 
  5.   [[NSUserDefaults standardUserDefaults] setBool:shouldBeEnabled forKey:@"com.raywenderlich.Rayrolling.shouldbeEnable"]; 

你已經有了持久化你的選擇的邏輯,下面是將它關聯到GUI上去

回到Rayrolling.m中,修改-(void)doMenuAction的代碼為下面的:

  1. - (void)doMenuAction:(NSMenuItem *)menuItem { 
  2.   [Rayrolling setIsEnabled:![Rayrolling isEnabled]]; 
  3.   menuItem.title = [Rayrolling isEnabled] ? @"Disable Rayrolling" : @"Enable Rayrolling";  

這是個用來切換的bool值,啟用或者禁用Rayrolling

***,更改在didApplicationFinishLaunchingNotification:中的菜單項的初始化代碼,改為如下:

  1. NSMenuItem *menuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"]; 
  2. if (menuItem) { 
  3.   [[menuItem submenu] addItem:[NSMenuItem separatorItem]]; 
  4.   NSString *title = [Rayrolling isEnabled] ? @"Disable Rayrolling" : @"Enable Rayrolling"
  5.   NSMenuItem *actionMenuItem = [[NSMenuItem alloc] initWithTitle:title action:@selector(doMenuAction:) keyEquivalent:@""]; 
  6.   [actionMenuItem setTarget:self]; 
  7.   [[menuItem submenu] addItem:actionMenuItem]; 

這個菜單項將會保留你選擇的是否啟用的邏輯,即使Xcode重啟后也沒關系,因為你的選擇已經持久化存儲了。

導航到文件NSObject+Rayrolling_DVTBezelAlertPanel.m,添加一行頭文件

  1. #import "Rayrolling.h" 

***,打開方法Rayrolling_initWithIcon:message:parentWindow:duration:,將

  1. if (arg1) { 

替換為

  1. if ([Rayrolling isEnabled] && arg1) { 

構建并運行程序,以便更改插件的行為。

現在,你創建了一個可以用來改變xcode編譯成功/失敗提示框圖標和內容的插件,并且它還是能夠被選擇是否打開。這一天的工作成果相當不錯,難道不是嗎???

接下來做什么?

你可以從這里 下載完整的demo工程

你已經取得了很大的進步,但是依然有很多事情要做!在本教程的第二部分,你將會學習到DTrace基本知識,并且深入到一些LLDB的高級特性,諸如查找正在運行的進程,比如正在運行的xcode進程。

如果你想更進一步,那么在你前往教程3之前,你還有一些工作要做。在教程3中,你將會看到大量的匯編代碼。確保在這之前,你已經開始了解了相關的x86_64匯編知識,這里有2篇Mike Ash的介紹分析匯編的系列文章 文章1文章2,可以給你提供相關的幫助。

責任編輯:倪明 來源: CocoaChina
相關推薦

2015-08-06 13:44:21

swiftcocoapods

2012-11-19 11:07:42

IBMdw

2009-07-29 09:58:38

民工通過CCNACCNA

2011-07-20 10:02:01

Xcode cocoa 窗口

2020-09-02 07:22:17

JavaScript插件框架

2011-10-19 09:30:23

jQuery

2018-01-04 16:04:35

圓環放大動畫

2013-04-08 10:54:51

Javascript

2010-03-08 14:09:50

Linux雙網卡

2020-10-12 10:58:15

IDEA插件監聽

2023-12-12 08:08:17

插件PRPulsar

2009-08-19 04:14:00

線性鏈表

2009-08-19 14:15:42

C# 復合控件

2011-05-11 10:58:39

iOS

2023-05-10 08:05:41

GoWeb應用

2021-12-30 09:40:33

CentOS家庭實驗室Linux

2012-08-14 10:44:52

解釋器編程

2010-11-17 15:43:55

軟件測試Bug

2010-04-13 00:02:22

Visual StudIDE

2017-08-17 14:38:39

JavaAbstract抽象
點贊
收藏

51CTO技術棧公眾號

亚洲欧美综合图区| 精品一区二区久久| 亚洲精品电影久久久| 一本大道熟女人妻中文字幕在线| 日本电影一区二区在线观看| 日韩成人一级大片| 久久亚洲综合国产精品99麻豆精品福利 | gogogo高清在线观看一区二区| 欧美日韩国产高清一区| 妞干网视频在线观看| 男女视频在线观看免费| 狠狠色狠狠色综合系列| 欧美一区三区三区高中清蜜桃| 国产精品免费无码| 亚洲网一区二区三区| 日本高清无吗v一区| 黄色小视频大全| 国产永久免费高清在线观看| 国产精品亚洲第一| 欧美专区第一页| 欧美日韩亚洲国产另类| 欧美猛男做受videos| 日韩欧美国产综合一区| 欧美黄色一级片视频| 亚洲卡一卡二| 中文字幕欧美三区| 久久精精品视频| 国产区精品在线| 爽好多水快深点欧美视频| 欧美精品在线观看91| 亚洲精品一区二区三区影院忠贞| 成人在线超碰| 日韩一区二区三区在线观看| 日韩中文字幕组| aa视频在线观看| 亚洲日本在线天堂| 日韩福利二区| 色播色播色播色播色播在线| 粉嫩av一区二区三区在线播放| 国产精品免费在线免费| 国产精品午夜影院| 在线看片欧美| 欧美风情在线观看| 丰满少妇被猛烈进入一区二区| 国产精品一国产精品| 精品香蕉一区二区三区| 亚洲精品乱码久久久久久蜜桃图片| 日韩一区二区三区四区五区 | 久久久天堂av| 国产精品一区二区三区四区五区 | 国产无精乱码一区二区三区| 中文av一区| 久久综合伊人77777尤物| 毛片久久久久久| 日韩精品首页| 自拍偷拍亚洲在线| 特级西西人体高清大胆| 久久免费大视频| 在线视频中文亚洲| 欧美黄色高清视频| 成人同人动漫免费观看| 亚洲视频一区二区三区| 亚洲第一成人网站| 久9久9色综合| 国产亚洲欧洲高清| 蜜臀久久99精品久久久久久| 欧美亚洲国产一区| 日韩中文在线视频| 免费中文字幕日韩| 亚洲最大av| 欧美福利在线观看| 亚洲精品午夜久久久久久久| 亚洲免费成人| 日本欧美中文字幕| 一区二区乱子伦在线播放| 看电视剧不卡顿的网站| 91欧美日韩一区| 午夜精品小视频| www.日韩精品| 日韩免费av电影| 免费大片黄在线| 亚洲在线中文字幕| 日本国产在线播放| 精品国模一区二区三区| 欧美日韩卡一卡二| 性一交一黄一片| 天美av一区二区三区久久| 亚洲色图日韩av| 日本激情视频一区二区三区| 欧美涩涩网站| 欧美一区二区色| 伊人精品一区二区三区| 国产精品一区二区男女羞羞无遮挡 | 一区二区传媒有限公司| 日韩av大片站长工具| 欧美三级在线播放| 原创真实夫妻啪啪av| 欧美电影在线观看完整版| 亚洲天堂av高清| 在线看的片片片免费| 日韩视频一区| 国产日韩欧美夫妻视频在线观看| 国内精品国产成人国产三级| 成人av资源网站| 婷婷久久伊人| 菠萝蜜视频在线观看www入口| 色悠悠久久综合| 国产一级二级av| 精品国产乱码久久久久久果冻传媒 | 91免费在线看片| 国内精品久久久久久久影视麻豆 | 99久热这里只有精品视频免费观看| 亚洲国产三级网| 免费成人深夜蜜桃视频| 日韩网站在线| 91香蕉亚洲精品| 久草福利在线| 亚洲一区二区三区不卡国产欧美| 国产福利一区视频| 国产一级成人av| 美女久久久久久久| www.日韩一区| 国产91精品一区二区麻豆亚洲| 五月天色一区| 手机av在线| 日韩三级在线观看| 国产成人免费在线观看视频| 国产欧美日韩综合一区在线播放| 成人动漫网站在线观看| 你懂的在线观看| 亚洲国产wwwccc36天堂| 色婷婷一区二区三区av免费看| 亚洲日本三级| 久久久久久久久久婷婷| 国产一区二区网站| 91天堂素人约啪| 国产美女主播在线播放 | 午夜影院日韩| 国产激情美女久久久久久吹潮| 黄色动漫在线| 欧美人成免费网站| 亚洲一级理论片| 日本91福利区| 日韩精品久久一区二区三区| 日本三级一区| 亚洲精品有码在线| 欧美三级一区二区三区| 成人av中文字幕| 草草视频在线免费观看| 一本色道69色精品综合久久| 欧美成年人在线观看| 97人妻精品一区二区三区软件 | 欧美丰满熟妇bbbbbb| 精品一区二区三区久久| 亚洲一区二区三区午夜| 成人国产在线| 日韩在线视频观看| 国产老女人乱淫免费| 亚洲激情自拍偷拍| 九色91porny| 国产精品久久天天影视| 成人综合网网址| 91国内在线| 欧美成人精品二区三区99精品| 九九热国产精品视频| 国产99精品在线观看| 久操网在线观看| 免费福利视频一区| 992tv成人免费影院| 亚洲av毛片成人精品| 一本久久综合亚洲鲁鲁五月天| 日韩中文字幕有码| 蜜桃av一区二区在线观看| 日本特级黄色大片| 911精品国产| 91精品国产91久久久久久吃药 | 欧美精品videos| 婷婷视频在线观看| 91成人免费网站| 亚洲人与黑人屁股眼交| 国产九九视频一区二区三区| 久久精品在线免费视频| 波多野结衣一区二区三区免费视频| 97精品在线视频| 超碰免费97在线观看| 欧美一级片在线| 国产尤物在线视频| 国产精品第四页| 亚洲中文字幕无码一区| 日韩电影网1区2区| 在线观看污视频| 综合国产视频| 91午夜理伦私人影院| 中文字幕人成乱码在线观看 | 免费在线国产视频| 亚洲色图25p| jlzzjlzzjlzz亚洲人| 欧美日韩国产在线| 日本少妇aaa| 26uuu亚洲综合色欧美| 在线观看免费不卡av| 99在线观看免费视频精品观看| 日韩高清国产一区在线观看| 99久久香蕉| 成人黄色在线播放| 日本三级一区| 九九热99久久久国产盗摄| 黄色片在线免费观看| 欧美xxxx在线观看| 在线免费看av的网站| 黄网动漫久久久| 天天看天天摸天天操| 国产亚洲综合av| 91精品又粗又猛又爽| 捆绑紧缚一区二区三区视频| 国产精品50p| 欧美激情偷拍| 一区二区冒白浆视频| 久久久久久毛片免费看| 亚洲伊人久久大香线蕉av| 蜜桃精品在线| 26uuu另类亚洲欧美日本一| 国产乱色在线观看| 这里只有精品在线观看| 亚洲色图另类小说| 精品国产不卡一区二区三区| 亚洲一区二区人妻| 色哟哟精品一区| 日本特黄特色aaa大片免费| 亚洲另类中文字| 一本色道久久88| 欧美激情中文不卡| 欧美激情aaa| www国产精品av| 中文在线永久免费观看| 国产成a人亚洲精品| 免费不卡av网站| 国模大尺度一区二区三区| 亚洲激情在线观看视频| 久久一二三四| 99精品免费在线观看| 亚洲一区日韩在线| 亚洲人精品午夜射精日韩| 在线观看的日韩av| 成人性生活视频免费看| 国产精品xvideos88| 精品久久久无码人妻字幂| 婷婷另类小说| 大地资源第二页在线观看高清版| 999国产精品999久久久久久| 性欧美.com| 99精品综合| 国产在线无码精品| 欧美在线亚洲综合一区| 成人手机在线播放| 欧美一区免费| 亚洲理论电影在线观看| 亚洲一级高清| 91专区在线观看| 香蕉亚洲视频| 黄色三级视频片| 免费xxxx性欧美18vr| 亚洲免费成人在线视频| 韩国成人在线视频| xxxxwww一片| 99精品久久久久久| 久久久久久九九九九九| 日本一区二区三区国色天香| 成人精品一二三区| 亚洲少妇最新在线视频| 强行糟蹋人妻hd中文| 亚洲福利视频三区| 人人爽人人爽人人片av| 欧美三级中文字幕| 性网爆门事件集合av| 日韩av一区二区在线| 久久久久久久影视| 日韩视频在线免费观看| 成人看片免费| 77777亚洲午夜久久多人| 日韩成人亚洲| 亚洲free性xxxx护士hd| 日韩高清影视在线观看| 性欧美大战久久久久久久免费观看| 久久久久蜜桃| 五月丁香综合缴情六月小说| 日韩精品一二三| 激情成人在线观看| 99精品视频在线观看| 日本一二三不卡视频| 亚洲精品国久久99热| 日韩精品国产一区二区| 欧美私人免费视频| 亚洲国产视频一区二区三区| 亚洲女人天堂色在线7777| 日本美女在线中文版| 国模gogo一区二区大胆私拍| 日韩不卡视频在线观看| 成人区精品一区二区| 国产一区毛片| 国产精品久久国产| 日韩高清在线电影| 野战少妇38p| 国产精品国产自产拍高清av| 粉嫩aⅴ一区二区三区| 欧美日韩电影一区| 亚洲欧洲综合在线| 麻豆国产va免费精品高清在线| a一区二区三区| 91免费版网站在线观看| av亚洲免费| 欧美成人高潮一二区在线看| 国产在线一区二区综合免费视频| 国产麻豆天美果冻无码视频| 亚洲另类春色国产| 中文字幕777| 亚洲另类图片色| 国产乱码在线| 91在线网站视频| 波多野结衣在线观看一区二区| 欧美又粗又长又爽做受| 久久99国内精品| 亚洲国产av一区| 香蕉成人伊视频在线观看| 国产一区二区三区中文字幕 | 久草免费在线| 国产精品久久久久久久久借妻| 欧美人成在线观看ccc36| 国产91在线亚洲| 黄页视频在线91| 国产精品av久久久久久无| 精品国产福利在线| 亚洲精品国产一区二| 久久亚洲精品小早川怜子66| 国产麻豆一区| 神马影院一区二区三区| 可以免费看不卡的av网站| 国产iv一区二区三区| 亚洲欧洲日本国产| 99综合精品| 国产精九九网站漫画| 亚洲视频免费在线观看| 中文字幕资源网| 在线国产精品视频| 波多视频一区| 久久手机视频| 国产精品女主播一区二区三区| 麻豆tv在线观看| 夜夜精品浪潮av一区二区三区| 国产永久免费视频| 日韩最新中文字幕电影免费看| 另类一区二区| 在线看无码的免费网站| 久久爱另类一区二区小说| 少妇视频在线播放| 91精品办公室少妇高潮对白| 毛片在线播放网址| 欧洲成人在线观看| 美女精品一区最新中文字幕一区二区三区 | 日本在线一区二区三区| 中国老女人av| 大尺度一区二区| 日韩欧美激情视频| 亚洲国产欧美一区二区三区同亚洲 | 女性隐私黄www网站视频| 国产亚洲精品7777| 中文字幕精品在线观看| www.日韩系列| 一区二区三区视频播放| 久久亚洲精品无码va白人极品| 99精品久久久久久| 日本熟妇一区二区三区| 日韩在线观看免费高清完整版| 91麻豆精品国产91久久久更新资源速度超快| 亚洲国产精品一区二区第一页 | 91精品美女在线| 国产精品mm| www.超碰97| 91国偷自产一区二区三区成为亚洲经典 | 久久精品视频在线播放| 亚洲网址在线观看| 日韩a在线播放| 中文字幕日韩欧美一区二区三区| 国产黄色片免费观看| 欧美一区二区三区免费视| 日韩大片在线播放| 在线观看日本www| 亚州成人在线电影| 亚洲精品传媒| 国产高清精品一区二区三区| 久久综合伊人| 麻豆成人在线视频| 亚洲色图在线观看| 日韩精品视频一区二区三区| 99爱视频在线| 亚洲精品国产品国语在线app| 天堂91在线| 成人国产精品一区| 国产精品久久久久毛片大屁完整版| 国产成人免费观看网站|