iOS崩溃处理机制:NSTimer Crash防护

产生的原因:
NSTimer会 强引用 target实例,所以需要在合适的时机invalidate 定时器,否则就会由于定时器timer强引用target的关系导致 target不能被释放,造成内存泄露,甚至在定时任务触发时导致crash。与此同时,如果NSTimer是无限重复的执行一个任务的话,也有可能导致target的selector一直被重复调用且处于无效状态,对app的CPU,内存等性能方面均是没有必要的浪费。所以,很有必要设计出一种方案,可以有效的防护NSTimer的滥用问题。

解决方案:
定义一个抽象类,NSTimer实例强引用抽象类,而在抽象类中,弱引用target,这样target和NSTimer之间的关系也就是弱引用了,意味着target可以自由的释放,从而解决了循环引用的问题。

具体方式:
1、定义一个抽象类,抽象类中弱引用target。

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface XZProxy : NSProxy

+ (instancetype)proxyWithTarget:(id)target;

@end

NS_ASSUME_NONNULL_END
#import "XZProxy.h"


@interface XZProxy ()

/// 消息转发的对象
@property (nonatomic, weak) id target;

@end

@implementation XZProxy

+ (instancetype)proxyWithTarget:(id)target {
    // NSProxy没有init方法, 只需要调用alloc创建对象即可
    XZProxy *proxy = [XZProxy alloc];
    proxy.target = target;
    return proxy;
}

- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}

@end

2、创建category,交换系统方法,实现NSTimer强引用抽象类。
ps:也可以不使用分类,不用交换方法,直接在创建timer实例的时候,将原本的target指向抽象类即可

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (NSTimerCrash)

+ (void)xz_enableTimerProtector;

@end

NS_ASSUME_NONNULL_END
#import "NSObject+NSTimerCrash.h"
#import "NSObject+XZSwizzle.h"
#import "XZProxy.h"

@implementation NSObject (NSTimerCrash)


+ (void)xz_enableTimerProtector {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        // 创建一个timer并把它指定到一个默认的runloop模式中,并且在 TimeInterval时间后 启动定时器
        [NSTimer xz_classSwizzleMethod:@selector(scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:) replaceMethod:@selector(xz_scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:)];
        
        // 创建一个定时器,但是么有添加到运行循环,我们需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法。
        [NSTimer xz_classSwizzleMethod:@selector(timerWithTimeInterval:target:selector:userInfo:repeats:) replaceMethod:@selector(xz_timerWithTimeInterval:target:selector:userInfo:repeats:)];
    });
}


+ (NSTimer *)xz_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {
    
    return [self xz_scheduledTimerWithTimeInterval:timeInterval target:[XZProxy proxyWithTarget:target] selector:selector userInfo:userInfo repeats:repeats];
}

+ (NSTimer *)xz_timerWithTimeInterval:(NSTimeInterval)timeInterval target:(id)target selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
    
    return [self xz_timerWithTimeInterval:timeInterval target:[XZProxy proxyWithTarget:target] selector:aSelector userInfo:userInfo repeats:yesOrNo];
}

@end
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 应用程序的崩溃总是最让人头疼的问题,也是非常严重的研发事故,那么应该如果降低程序的崩溃率呢?这里就用到了“APP运...
    光之盐汽水阅读 2,592评论 0 4
  • 牛犊子们好,我是shark, 老规矩,上问题,搞懵逼你们 1: 咋滚动下UI定时器就不起效了?2: NSTime...
    谌文阅读 863评论 0 0
  • 本文内容系全文转载自微信开发团队的《iOS 事件处理机制与图像渲染过程》 目录 iOS 事件处理机制与图像渲染过程...
    Vinc阅读 1,610评论 1 20
  • iOS 事件处理机制与图像渲染过程 iOS RunLoop都干了什么 iOS 为什么必须在主线程中操作UI 事件响...
    yaoyao_IOS阅读 565评论 0 6
  • 1、NSTimer的基本使用方法 首先我们看那一下系统提供给我们的基本的使用方法。 基本的注释都已经说明了。下面来...
    Niko_peng阅读 1,402评论 0 0