method Swizzling实践--alloc hooker

  废话不多说,直接进入正题。这次碰到的问题主要是在项目里涉及到的一些内存问题,先说说这个项目的整体结构,由于要实现跨平台的缘故,所以在最顶层是由lua来编写相关的业务代码的,实现安卓和iOS端业务的快速开发,中间是一层统一的c++接口,然后iOS跟安卓根据这份统一的c++接口实现各自平台的底层逻辑代码,比如对于一个View,c++的统一接口为:

1
void *createView(const char *type)

然后创建view由iOS跟安卓各自去实现,iOS端可以实现c++跟oc的混合编码,可以直接调用oc的代码,然后lua调用c++借由swig实现互调,这个有很多种方法可以自己去挑选合适的。
  下面说说框架中遇到的问题,由于这中间涉及到lua,c++跟oc的混合编码,而且oc使用的是mrc方式(项目开始的时间比较早),因此内存管理的问题变得尤为重要。正常的处理逻辑是:创建时,lua调用c++去new一个oc对象;释放时,lua调用c++的析构函数,然后在析构函数里完成oc对象的释放。然后怎么确保这些对象按照这个逻辑被释放掉了呢~

  对于这个问题,我采取的策略是在每个对象创建的时候做一个记录,然后在对象被清理的时候从记录中删除,最后看看记录中还有没有多余的对象,以此来判断对象是否都被释放完毕(或者有啥新的思路也可以提出来哈~),实现这个需要借助两个东西:method SwizzlingNSHashTable,method Swizzling可以参考之前的博客,NSHashTable主要利用它可以持有weak类型的成员变量,NSHashTableWeakMemory表示添加到该hash table时并不会增加对象的引用技术,当添加的对象在外部被释放时,该table会自动删除相应的对象。

1
NSHashTable *hashTable = [NSHashTable hashTableWithOptions: NSHashTableWeakMemory];

因此可以看出具体的实现方式了:利用method Swizzling去hook nsobject的init方法,并将其添加到NSHashTableWeakMemory型的NSHashTable,最后看看该table中还有哪些对象,证明这些对象还没有被释放掉,可以通过这来检测整个对象的创建跟释放逻辑是否正确。
  下面给出示例代码,比如要记录UIView对象创建跟释放:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static NSHashTable *hashTable;//存储创建的对象
@implementation UIView (AllocHook)

//method Swizzling的方法,我们hook该方法,并额外将该对象添加到NSHashTable中
-(instancetype)customInit
{
if (!hashTable) {
hashTable = [NSHashTable hashTableWithOptions:NSHashTableWeakMemory];
}
[hashTable addObject:self];
return [self customInit];
}
@end

//一个hook对象,用于开启method Swizzling
@implementation ViewAllocHooker

-(void)startRecord
{
Class class = [UIView class];

SEL originalSelector = @selector(init);
SEL swizzledSelector = @selector(customInit);

Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//交换原来的init方法跟新的customInit方法
method_exchangeImplementations(originalMethod, swizzledMethod);
}

这样每个调用init方法创建的UIView或其子类都将被记录到NSHashTable中,同时由于该table只是持有添加对象的弱引用,因此可以在后面查看该table还有哪些对象就可以确定这些对象还没被释放,以验证该框架是否正确。详细的例子代码可以查看github,其中的ViewAllocHooker可以采用单例,由于是示例代码就不更改了,有啥改进的地方欢迎提出~~