曾静的博客

但行好事,莫问前程.

嗨,我是曾静 (@devzeng),目前暂居深圳。


这是我用来记录平日学习笔记的地方,欢迎您的访问.

使用 InjectionIII 提高开发效率

iOS 原生代码的修改编译调试,都是一遍遍的 Command + R 重新编译重启 App 来进行的。一般来说,随着项目的复杂性增加,代码量越大,编译的耗时就越久。大多数项目都采用二进制集成的方式(将部分代码/组件库先编译成二进制的库集成到工程里面),来避免每次都全量编译来提升编译的速度,但即使这样也没有解决每次修改代码(比如对UI的进行还原度调整)还是需要重新编译的情况。

一直很羡慕前端开发或者是Flutter开发的同学,修改完代码就能直接看到效果。官方的效果动画如下:

flutter_hot_reload

如果对于 iOS 原生开发来说要如何实现 Flutter 的这种极速调试开发体验呢?由国外的一位大神John Holdsworth 开发的 InjectionIII 这个工具,可以动态地将 Swift 或 Objective-C 的代码在已运行的程序中执行,以加快调试速度,同时保证程序不用重启。官方的效果动画如下:

ios_hot_reload

之前只是简单试用了一下对于组件化开发的项目来说确实没有太好的效果,因为我们的项目源码基本上都分散在多个仓库里面。近期看了一下源码,结合工具的一些特性实现了在我们的项目里面集成,并推广应用起来,从大家的反馈来看确实能提高研发的效率,基本上能做到修改完代码马上能看到效果。

如何使用

1、安装 InjectionIII.app

可以到AppStoreGithub Release下载,推荐到Github Release页面下载一般更新比较及时 ,当前的最新版本是 4.7.5。

download_app

2、设置项目目录

安装完成后,打开 InjectionIII.app ,在状态栏点图标在弹框页面中选择 Open Project,设置当前正在开发的项目目录(xcodeproj文件所在的目录)。

open_project_settings

设置的项目会在 Open Recent 中展示,同时需要保持 File Watcher 的选项选中。

file_watcher_settings

说明:如果需要切换其他项目,可以 Clean Menu 清除当前选中的项目,然后重新设置。

3、项目配置

(1)AppDelegate 的 didFinishLaunchingWithOptions 方法中添加注入代码

以 OC版本 为例,Swift的可以参考官方的文档,基本上也是一样的做法:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#if DEBUG
   [[NSBundle bundleWithPath:@"/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle"] load];
#endif

   ...
}

运行项目,控制台出现 Injection connected, watching xxxx 表示配置成功。

说明:同时还支持 tvOS(tvOSInjection.bundle) 和 MacOS(macOSInjection.bundle)版本,只要在对应的路径进行替换即可

(2) 监听变化并刷新

1) 添加 INJECTION_BUNDLE_NOTIFICATION 通知

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (instancetype)init {
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(injectedAction:) name:@"INJECTION_BUNDLE_NOTIFICATION" object:nil];
    }
    
    return self;
}

- (void)injectedAction:(id)sender {
    // TODO: 刷新UI
}

2)添加 injected 方法

- (void)injected {
    // TODO: 刷新UI
}

项目整合

如果利用 CocoaPods 组件化的开发模式,可能存在通过 CocoaPods 引入一些外部的代码到项目里面来进行开发。找到了一个issue,作者做了回复就是推荐将源码放到工程目录下面,这样就可以实现自动监听。

cocoapods_sources_settings

这就是我前面提到的之前试了确实对我们的项目不太友好的原因,我们是通过可视化工具进行项目的一些管理的,代码的结构调整起来会比较繁琐。

知道需要通过设置 Open Project 来绑定一个项目的工作空间目录,InjectionIII 会自动监听文件的变化,刚开始想的就是能不能改下代码,自动去解析项目额外依赖的源码目录,然后加入到监听的列表中呢? 下载源码到本地看下:

git clone https://github.com/johnno1962/InjectionIII --recurse-submodules

首先看下 Open Project 具体干了什么?通过翻阅代码发现其实主要是拉起来一个文件目录选择对话框,获取选中的目录赋值给一个全局的变量 selectedProject

let open = NSOpenPanel()
open.prompt = "Select Project File"
open.directoryURL = url
open.canChooseDirectories = false
open.canChooseFiles = true
// open.showsHiddenFiles = TRUE;
if open.runModal() == .OK,
	let url = open.url {
	selectedProject = url.path
}

然后下面就出现一段将 selectedProject 加入到 watchedDirectories 的代码:

watchedDirectories.removeAll()
watchedDirectories.insert(url.path)
if let alsoWatch = defaults.string(forKey: "addDirectory"),
	let resolved = resolve(path: alsoWatch) {
	watchedDirectories.insert(resolved.path)
}

watchedDirectories 的定义 var watchedDirectories = Set<String>() 从变量名就可以看出来是监听的文件夹列表,把项目目录加进去很好理解,但是 addDirectory 这个又是啥?经过追踪代码,发现新版本添加了一个 Add Directory 的设置项用于设置一个额外的目录用于监听代码的变动。

@IBAction func addDirectory(_ sender: Any) {
	let open = NSOpenPanel()
	open.prompt = openProject
	open.allowsMultipleSelection = true
	open.canChooseDirectories = true
	open.canChooseFiles = false
	if open.runModal() == .OK {
		for url in open.urls {
			appDelegate.watchedDirectories.insert(url.path)
			self.lastConnection?.watchDirectory(url.path)
			persist(url: url)
		}
	}
}

对照的功能入口就是:

add_directory_settings

这样我们就可以把那些外部引用的代码都放在一个固定的目录下面(其实我们现在也是这样做的)然后在 Add Directory 里面设置这个源码根目录就好了。当然前面说的可以通过自动解析我们的配置文件然后实现自动加入监听也是可以实现的。

前面提到了如果要实现动态刷新UI需要添加监听代码,这个确实存在对项目有一定的侵入性,如何解决呢?

1、自动注入 iOSInjection.bundle

可以通过在 +load 方法中监听 UIApplicationDidFinishLaunchingNotification 通知的方法搞定

2、UIViewController 实现自动刷新

可以通过 hook UIViewController 的 init 方法动态添加通知监听实现。

整合了一些代码,写了一个简单的Demo:

pod 'InjectionIIISupport', :git => 'https://github.com/hhtczengjing/InjectionIIISupport.git', :configuration => ['Debug']

问题排查与解决

说明:xib/storyboard 目前测试有点问题,后续再补充

1、控制台出现 Loaded .dylib - Ignore any duplicate class warning ^ 的日志

解决办法:无需关注,不影响使用

2、添加了 injected 方法不生效或者是直接Crash

解决办法:监听 INJECTION_BUNDLE_NOTIFICATION 通知实现

3、控制台出现 Your project file seems to be in the Desktop or Documents folder and may prevent InjectionIII working as it has special permissions.

解决办法:无需关注,不影响使用

4、如果修改的 UITableViewCell 的内容,保存代码后不生效

解决办法:重新打开页面,或者是上下滚动页面

参考资料

最近的文章

iOS推送设置个性化消息提示音

更新到 iOS 17 后,不少用户反馈默认的通知声音不一样了,觉得新的提示音太安静了,以至于错过某些关键的通知。很可惜的是我们无法指导用户通过设置更改 App 默认的通知铃声,只能更改系统自带APP相关的提示音(如:电话、短信、提醒、收到新邮件声等)。部分用户表示微信可以修改推送的铃声,而且是修改完立即就生效了。设置界面效果:首先想到的是 APNs 推送可以指定 sound 字段,用于设置用户的铃声,大多数APP使用的跟随系统默认就是 default。示例数据:{ "aps" : {...…

iOS继续阅读
更早的文章

搭建内部技术文档搜索服务

近几年团队一直在坚持在内部的 Confluence 平台上写技术文档,使用过程中发现文档的利用率较低,主要表现在如下方面: (1)目录层级深,找到文档入口需要花费一番功夫 (2)搜索范围大,精细化的搜索使用成本较高 (3)各类技术文档缺乏分类,散落在各处为了解决这些痛点,设计了一个仅用于团队内部的技术文档管理方案。主要是实现一个数据采集器将组内的全部技术文档采集到 Elasticsearch 里面,然后利用 Elasticsearch 开箱即用的能力实现一个简易的搜索服务。整个架构如...…

Note继续阅读