OC

React Native的一次Crash踩坑

Posted by sunzhongliang on November 21, 2019

发现问题

某日,APP监控平台突然显示每日有大量Crash,崩溃率达到了1%多,已经影响到了2000多个用户了,都集中在iOS13系统下,iPhone6/6s/7/7Plus上,查看调用堆栈: 大量的错误信息指向了这里-[RCTWeakProxy displayDidRefresh:]: unrecognized selector sent to instance
查看RCTWeakProxy源码显示,RCTWeakProxy是一个消息转发中间者,目的是为了处理循环引用导致的内存不释放,将SEL转发到消息接收者身上
而持有RCTWeakProxy的是RCTUIImageViewAnimated,其源码如下:

RCTUIImageViewAnimated是一个用来处理gif图片解码的view; CADisplayLink会对target产生强引用,为了处理循环引用,常见的做法是是采用一个中间者将消息再转发回来,这里处理的也没有异议。


而crash调用堆栈显示RCTWeakProxy不能响应displayDidRefresh:方法
也就是说RCTWeakProxy没有将方法转发出去,自己来实现这个方法了,所以就报错了unrecognized selector sent to instance
到了这里就百思不得其解了,画个图来表示一下引用关系: 也就是说RCTWeakProxy弱引用了RCTUIImageViewAnimated,一旦RCTUIImageViewAnimated释放了,RCTWeakProxy自然也就释放了,可为什么堆栈信息还会指向这里呢?网上搜索也没有找到响应的问题,RN的github上也没有相应的issues,难不成是自己使用的姿势有问题?

解决问题

找了半天原因,似乎是跟异步线程释放了RCTUIImageViewAnimated有关,而线上崩溃率一直居高不下,为了尽快平复crash率,先暂时把这个crash压下来,这里利用了runtime的消息转发,在最后一步方法签名这一阶段捕捉到未响应的方法,然后将SEL转发到一个空方法上去实现

@implementation RCTWeakProxyCrashProtect

- (void)CrashProtectCollectCrashMessages {
    
}

@end

@implementation RCTWeakProxy (SafeSelector)

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    anInvocation.selector = @selector(CrashProtectCollectCrashMessages);
    [anInvocation invokeWithTarget:[[RCTWeakProxyCrashProtect alloc] init]];
    
    // 在这里记录日志,因为CADisPlayLink每秒会大量调用,为了避免把日志系统压垮,这里使用了整个APP生命周期内只记录一次的做法
    static dispatch_once_t oncet;
    dispatch_once(&oncet, ^{
        // 日志
    });
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [RCTWeakProxyCrashProtect instanceMethodSignatureForSelector:@selector(CrashProtectCollectCrashMessages)];
    return signature;
}

疑问?

对于一个大厂写的代码,RCTWeakProxy为何要继承NSObject,而不是继承NSProxy呢?
NSProxy相比NSObject来说在转发消息上面更有优势,效率也更加高效~~~~

本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.