插件地址:https://github.com/johnno1962/injectionforxcode
實(shí)現(xiàn)功能
Injection Plugin For Xcode 是 Xcode 上的一個(gè)插件。利用它可以修改程序代碼,實(shí)時(shí)在模擬器或?qū)崣C(jī)上看到效果而不需要對(duì)程序進(jìn)行重新編譯。

安裝方法
第一步:移除簽名
從Xcode8開(kāi)始,蘋(píng)果引入了官方的擴(kuò)展API,但目前只局限于資源編輯器擴(kuò)展,蘋(píng)果還將Xcode簽名來(lái)阻止未簽名的代碼的注入與執(zhí)行。所以但也阻止了一些類(lèi)似于injection的工具的安裝。網(wǎng)上提供的Xcode8以上版本安裝插件的解決方案:
- update_xcode_plugins(未嘗試)
- MakeXcodeGr8Again(未嘗試)
- xcunsign(最高支持Mac OS 10.12版本;未嘗試)
- 自簽名
由于我只嘗試了自簽名的方法,所以下面詳細(xì)介紹一下自簽名的具體步驟:
- 打開(kāi)電腦里面鑰匙串;

- 選擇創(chuàng)建一個(gè)證書(shū)(在鑰匙串訪問(wèn) ->證書(shū)助理);

- 輸入你的名字(你喜歡的名字),然后選擇“代碼簽名”的證書(shū)類(lèi)型。不是必需的,但該名稱(chēng)在命令行以后使用,因此可以更好地用在這里很容易區(qū)分的名稱(chēng)(我在這里使用XcodeSigner);

- 在終端中輸入如下代碼對(duì)Xcode進(jìn)行重新簽名,等待十幾分鐘即可。
sudo codesign -f -s XcodeSigner /Applications/Xcode.app
但是移除簽名可能會(huì)無(wú)法保證安全性,并且會(huì)影響應(yīng)用程序的發(fā)布,所以可以在重新簽名之前拷貝一個(gè)Xcode的副本,專(zhuān)門(mén)用于應(yīng)用程序的發(fā)布。有興趣的話(huà),你也可以嘗試一下前三種方法。
第二步:安裝插件
- 下載完之后,打開(kāi)紅圈選中的文件夾。

-
在終端中輸入如下代碼獲取當(dāng)前版本Xcode的UUID:
defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID打開(kāi)
info.plist,在plist文件中找到DVTPlugInCompatibilityUUIDs。如果剛才從終端中獲得的UUID已存在,則什么也不用做;反之,點(diǎn)擊+, 添加一個(gè)item, 對(duì)應(yīng)的value值為輸入剛才從終端中獲得的UUID,保存。UUID.png
- 打開(kāi)工程并運(yùn)行,成功之后即完成了安裝。

- 安裝后重啟Xcode,會(huì)發(fā)現(xiàn)在Product菜單下多了兩個(gè)選項(xiàng)(注意重啟的時(shí)候,應(yīng)該選擇load bundle,而不應(yīng)該選skip bundle,否則不能再Xcode中找到),即說(shuō)明安裝成功。

使用方法
修改源碼,按下刷新的快捷鍵ctrl+=。會(huì)有一個(gè)快速的進(jìn)度條閃過(guò),修改的代碼就生效了。
工作原理
它通過(guò)解析應(yīng)用的build日志來(lái)判斷源代碼文件上次是怎么被編譯的。然后會(huì)把這些重新編譯一遍包在一個(gè)已經(jīng)通過(guò)動(dòng)態(tài)加載器注入到應(yīng)用的bundle里。這個(gè)時(shí)候其實(shí)有兩個(gè)版本的類(lèi)在app里,一個(gè)原始的和一個(gè)修改過(guò)的版本。修改過(guò)的版本通過(guò)和原始類(lèi)“swizzled”來(lái)產(chǎn)生效果。swizzling利用了OC的runtime。這個(gè)也可以在Swift中沒(méi)有標(biāo)記為final或者private的方法(可以被重寫(xiě)的方法),但是對(duì)結(jié)構(gòu)體無(wú)效。以下為插件作者對(duì)原理說(shuō)明的原文:
Injection for Xcode is an extension to the Xcode IDE that allows you to patch the implementation of a class's method without having to restart the application.
It performs this by parsing the build logs of the application to determine how a source file was last compiled. With this it wraps the result of re-compiling into a bundle which is injected into the application using the dynamic loader. At this point there are two versions of a class in the app, the original and a new modified version from the bundle. The modified version is then "swizzled" onto the original class so changes take effect.
This swizzling takes advantage of the fact that Objective-C binds method invocations to implementations at run time. This can also be performed on Swift classes provided that the method or class is not final or private (i.e. the method can be overridden) by patching the class' "vtable". This excludes the injection of methods of structs.
遇到的問(wèn)題
我嘗試在公司的項(xiàng)目中使用Injection插件的時(shí)候,遇到了一個(gè)bundle has failed to load錯(cuò)誤,在控制臺(tái)中顯示的錯(cuò)誤信息是symbol not found,于是我去gitHub的issues中逐個(gè)閱讀發(fā)現(xiàn)了相同的問(wèn)題而插件作者給出的回答是

但是因?yàn)槲覉?bào)錯(cuò)的類(lèi)并不是第三方庫(kù)中的類(lèi),所以我覺(jué)應(yīng)該還有救。折騰了一下午最后發(fā)現(xiàn)只要把
Build Setting中的symbols hidden by default設(shè)置為NO,就不會(huì)再出現(xiàn)這個(gè)錯(cuò)誤了。不過(guò)并不是所有的工程都需要修改這個(gè)設(shè)置項(xiàng),似乎只有使用了靜態(tài)庫(kù)的工程才需要修改。我猜測(cè)原因可能是在解析build日志時(shí)由于symbol被隱藏,它無(wú)法找到調(diào)用靜態(tài)庫(kù)的類(lèi),從而出現(xiàn)這個(gè)問(wèn)題。
