跳过正文
  1. Posts/

runtime实现私有变量搜索

··2 分钟

本文 Demo 地址 2018.7.31

需求
#

在开发功能时,为了满足产品变态的需求,难免有系统类提供的API不够用的时候,这时候私有变量就可以发挥它光和热了。怎么通过一个类,一层一层的找到特定类型的私有成员变量? 受益于Objective-C的动态语言特性,就算苹果UIKit不开源,但是在runtime面前,类的结构还是暴露无遗。我的思路是逐层手动打印成员变量信息,如果是UI控件可以用Reveal来加快进度,配合KVC机制,获取私有变量就如同探囊取物一般。 在多次遇到这个问题后,我决定实现一个工具类来简化这个过程,毕竟能自动化的就尽量不要手动。

思路
#

搜索范围:成员变量、属性

关键步骤如下:

  1. runtime中的方法class_copyIvarList(),可以取出类的所有成员变量结构体Ivar
  2. 从Ivar中可以取出成员变量类型Type Encodings
  3. Type Encodings获取到类名
  4. 遍历类的所有Ivar,获取到类的所有成员变量的类信息

但是问题没这么简单,比如ClassA是ClassB的成员变量,ClassB是ClassC的成员变量,想通过ClassC找到ClassA,需要向下找两层才能找到,还要考虑父类的情况。 整个搜索过程就是一颗以待搜索类为根的树,可以用BFS来搜索,步骤如下(如图,数字是搜索顺序):

  1. 从根节点开始搜索
  2. 首先遍历当前节点的父类,加入搜索队列
  3. 再取出当前节点的所有成员变量,加入搜索队列
QQ20180731-202956@2x

如何从Ivar中获取Class
#

Ivar中不能直接取出对应类名,只能取出Type Encodings,间接可以得到类名。从官方文档截了张图,可以清晰的看到Code的定义。

基本数据类型、id类型、集合类型、结构体等类型就不需要搜索了,可以过滤掉。我们只搜索两种类型

  1. 类,例如@"UIWebViewInternal"
  2. 代理,例如@"<UIViewControllerTransitioningDelegate>"

优化及存在问题
#

  1. 可以用一个NSMutableSet存储已经搜索过的类,每次搜索前判断一下是否已经搜索过
  2. 记录搜索次数,可以限制搜索次数。
  3. 对于定义为id类型、集合类的成员变量没有做处理,可以继续深入遍历

流程示意图源文件

相关文章

iOS横竖屏总结

··3 分钟
2018.7.23 使用Auto Layout来进行布局就不用自己去监听横竖屏事件了,只需要绘制多套布局即可。但是项目有很多页面是自己手动计算的,于是只有想办法再旋转屏幕时重新布局。

记一个实现UIWindow子类的小坑

··1 分钟
2018.7.19 问题描述 # 项目中为了实现一个全局遮罩界面,使用了一个UIWindow的子类MyWindow,MyWindow为了实现回调定义了代理MyWindowDelegate。代码大致如下:

一个autoreleasepool的使用场景

··1 分钟
2018.7.18 今天在学习大佬博客的时候看到一个问题,下面代码会有什么问题? // largeNumber是一个很大的数 for (int i = 0; i < largeNumber; i++) { NSString *str = [NSString stringWithFormat:@"hello -%04d", i]; str = [str stringByAppendingString:@" - world"]; NSLog(@"%@", str); } 刚开始没看出什么问题,就是普通的循环,每次循环创建一个局部变量NSString。于是写了个Demo验证了下,在观察内存的时候发现了端倪,在循环过程中,内存不断飙升。

UITabbar自定义Badge

··1 分钟
2018.7.10 tabBarItem的Badge默认样式是带数字的,但是产品要求只要一个小红点,不需要数字,这就需要我们自定义Badge了。 用Reveal分析UITabBar,发现每个按钮是一个UITabBarButton,层级如下:

计算文字长度

··2 分钟
2018.7.9 官方文档 方法定义 # - (NSRect)boundingRectWithSize:(NSSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary<NSAttributedStringKey, id> *)attributes context:(NSStringDrawingContext *)context; 参数定义 # size # 绘制的限制size,计算出来的值不会超过这个大小。