插件地址:https://github.com/johnno1962/injectionforxcode
实现功能
Injection Plugin For Xcode 是 Xcode 上的一个插件。利用它可以修改程序代码,实时在模拟器或实机上看到效果而不需要对程序进行重新编译。
安装方法
第一步:移除签名
从Xcode8开始,苹果引入了官方的扩展API,但目前只局限于资源编辑器扩展,苹果还将Xcode签名来阻止未签名的代码的注入与执行。所以但也阻止了一些类似于injection的工具的安装。网上提供的Xcode8以上版本安装插件的解决方案:
- update_xcode_plugins(未尝试)
- MakeXcodeGr8Again(未尝试)
- xcunsign(最高支持Mac OS 10.12版本;未尝试)
- 自签名
由于我只尝试了自签名的方法,所以下面详细介绍一下自签名的具体步骤:
- 打开电脑里面钥匙串;
- 选择创建一个证书(在钥匙串访问 ->证书助理);
- 输入你的名字(你喜欢的名字),然后选择“代码签名”的证书类型。不是必需的,但该名称在命令行以后使用,因此可以更好地用在这里很容易区分的名称(我在这里使用XcodeSigner);
- 在终端中输入如下代码对Xcode进行重新签名,等待十几分钟即可。
sudo codesign -f -s XcodeSigner /Applications/Xcode.app
但是移除签名可能会无法保证安全性,并且会影响应用程序的发布,所以可以在重新签名之前拷贝一个Xcode的副本,专门用于应用程序的发布。有兴趣的话,你也可以尝试一下前三种方法。
第二步:安装插件
- 下载完之后,打开红圈选中的文件夹。
-
在终端中输入如下代码获取当前版本Xcode的UUID:
defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID
打开
info.plist
,在plist文件中找到DVTPlugInCompatibilityUUIDs
。如果刚才从终端中获得的UUID已存在,则什么也不用做;反之,点击+
, 添加一个item, 对应的value值为输入刚才从终端中获得的UUID,保存。
- 打开工程并运行,成功之后即完成了安装。
- 安装后重启Xcode,会发现在Product菜单下多了两个选项(注意重启的时候,应该选择load bundle,而不应该选skip bundle,否则不能再Xcode中找到),即说明安装成功。
使用方法
修改源码,按下刷新的快捷键ctrl
+=
。会有一个快速的进度条闪过,修改的代码就生效了。
工作原理
它通过解析应用的build日志来判断源代码文件上次是怎么被编译的。然后会把这些重新编译一遍包在一个已经通过动态加载器注入到应用的bundle里。这个时候其实有两个版本的类在app里,一个原始的和一个修改过的版本。修改过的版本通过和原始类“swizzled”来产生效果。swizzling利用了OC的runtime。这个也可以在Swift中没有标记为final
或者private
的方法(可以被重写的方法),但是对结构体无效。以下为插件作者对原理说明的原文:
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.
遇到的问题
我尝试在公司的项目中使用Injection插件的时候,遇到了一个bundle has failed to load
错误,在控制台中显示的错误信息是symbol not found
,于是我去gitHub的issues中逐个阅读发现了相同的问题而插件作者给出的回答是
但是因为我报错的类并不是第三方库中的类,所以我觉应该还有救。折腾了一下午最后发现只要把
Build Setting
中的symbols hidden by default
设置为NO
,就不会再出现这个错误了。不过并不是所有的工程都需要修改这个设置项,似乎只有使用了静态库的工程才需要修改。我猜测原因可能是在解析build日志时由于symbol被隐藏,它无法找到调用静态库的类,从而出现这个问题。