ARC理解后的实际运用

背景

最近在面试的时候,我始终会问“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方法以后,就不用去管它了。

最后附上修改后的反汇编代码:
jietu
其中objc_storeStrong源码是这样的

void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

发表评论