背景
最近在面试的时候,我始终会问“ARC的理解”,可能大家很奇怪为什么这个年代还会有人问这么老土的问题。是不是用上了ARC就可以不用知道内部实现的细节呢,是不是就算知道了细节在ARC环境下也没什么用武之地呢?我举一个在JSPatch上修复重写dealloc方法的例子。
问题
原来的JSPatch是不能重写dealloc方法的,因为会出现野指针。
我们看一下原来的代码,问题在哪里:
static void JPForwardInvocation(id slf, SEL selector, NSInvocation *invocation)
id slf
实际就是id __strong slf
,对于一般的方法调用其实一点问题也没有,但是dealloc不一样。编译器会在这个函数结束前插入release的代码,然而slf
已经调用完dealloc方法,这时再去release就是野指针错误。
改进
改为MRC实现的话工作量不少,为了重写dealloc这么做,太不划算。所以最后我还是基于ARC下,用了一些技巧来实现。
对比一下修复后的代码:
static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation)
参数assignSlf
用了__unsafe_unretained
,就可以理解成assign
对象,不持有,对计数没影响。由于调用的来源是可以保证这个assignSlf
刚进入函数的时候不会被释放,于是我只要立即用id slf = assignSlf
去持有,这个slf
就很安全了。因为一般声明的变量其实是隐式添加了__strong
(对象指针除外),实际就是id __strong slf = assignSlf
。
接下来,跳过JSPatch的业务代码,直接到
if (deallocFlag) {
slf = nil;
Class instClass = object_getClass(assignSlf);
if ([getPropIMP(assignSlf, nil, @"jp_origDeallocFlag") boolValue]) {
Method deallocMethod = class_getInstanceMethod(instClass, NSSelectorFromString(@"ORIGdealloc"));
void (*originalDealloc)(__unsafe_unretained id, SEL) = (__typeof__(originalDealloc))method_getImplementation(deallocMethod);
originalDealloc(assignSlf, NSSelectorFromString(@"dealloc"));
} else {
struct objc_super superInfo = {
.receiver = assignSlf,
.super_class = class_getSuperclass(instClass)
};
void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper;
msgSend(&superInfo, NSSelectorFromString(@"dealloc"));
}
}
关键句slf = nil
,这样ARC就会插入了release的代码,这样slf
就完成了使命。接下来就是assignSlf
的工作,由于assignSlf
是不会被编译器插入release代码的。所以这里手动调用完dealloc方法以后,就不用去管它了。
最后附上修改后的反汇编代码:
其中objc_storeStrong
源码是这样的
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}