What
我们知道苹果提供了手动给控件添加可访问性(Accessibility)的能力,然而这样做的工作量大且不一定能满足需求,只能另辟蹊径从技术自动化方向去尝试解决这个问题。
Why
由于组内有不少技术性项目(自动化测试、自动化埋点等…)都需要有唯一标识的能力,于是有了此次的探索。
How
在组内小范围讨论了一番并结合网络检索后,总结出以下2种方案:
视图栈方案: 运行时获得视图栈,动态生成唯一标识。脚本(配置)方案: 提前生成唯一标识的配置数据,然后在运行时进行绑定。
为了方便理解,接下来会用同一个案例来讲述两个方案的差别
如下图所示,有一个继承自UIViewController的类V1,其树形结构是用@property关键字声明的递归属性树(为了方便理解,已经由黑白名单策略过滤了私有变量,无效变量等节点)
矩形代表ViewController,圆形代表View,菱形代表Object
视图栈方案
先说视图栈方案,目前业内开源库里接受度较高的解决方案是TBUIAutoTest,其原理可以简述为:
- 如果是类的属性变量,则用属性的变量名作为唯一标识。
- 如果是局部变量,则用其来源的内容作为唯一标识。
图解如下:

该方案也暴露出以下问题:
被复用的视图D,在被复用的父视图A和父视图B中的变量名都是d1,用该方案则会出现同一个unique_id对应多个视图的情况。
虽然可以通过运行时的视图栈index来进行区分,但是会出现另一个问题,就是视图的层级可能发生变化,导致unique_id不稳定。
为了解决以上问题,也引出了接下来要重点说明的配置(脚本)方案。
配置(脚本)方案
要优化标识可能一致的问题,可以收敛要处理的类的范围,即只处理ViewController继承类的属性树,这也是该方案的关键点。
一般来说ViewController被复用的可能性不高,即使真的复用程度高,也可以配合后端下发唯一标识来解决相对定位的问题。
具体实现步骤如下:
1.生成配置数据
- 获得所有VC // objc_getClassList()
- 递归遍历VC持有的属性树 // class_copyPropertyList()
- 保存属性树上每个结点的keypath并生成其唯一标识 // md5(keypath)
2.保存格式化后的数据(json)
- 格式化保存VC的属性树数据(可以静态写入本地文件或保存在远端等)
|
|
3.关联配置数据
关联数据有两种情况
- 已知unique_id获取视图对象
因为之前已经生成好配置文件,那我们只需要在运行时索引配置表,通过unique_id找到对应的keypath,再通过KVC机制获取到对应的视图对象。
关键代码如下
|
|
- 已知视图对象获取unique_id
用runtime的提供的相关API找到对象的varName以及对象的ViewController,再通过配置表定位到他的unique_id。
关键代码如下:
|
|
同样我们再图解一番该方案:

用视图的keypath作为唯一标识(为了让unique_id长度一致,对其进行了md5编码)能保证在同一个VC下,被复用的视图的唯一标识不同,解决了视图栈方案的唯一标识重复问题。
被复用的视图D,在其父视图A下的唯一标识分别是md5(o1.a1.d1)和md5(o1.a2.d1),在父视图B中的唯一标识是md5(b1.d1)
模块设计图
介于尚未获得公司的开源许可,暂放一张模块设计图供大家参考。

TODO
最后再说明下目前脚本(配置)方案的局限性:只覆盖了通过@property方式或者iVar方式声明的属性。
以下是需要完善的地方及其解决方案的思考:
- 完善手动赋值情况: 提供生成唯一标识的规则
- VC被复用的场景: 可以通过服务器下发配合客户端赋值来解决
- 列表元素类型(UITableViewCell、UICollectionViewCell): 可以通过服务器下发配合客户端赋值来解决
- 局部变量: 可以复用TBUIAutoTest的方案临时解决(待完善)