注册

iOS --常见崩溃和防护(二)

接上一章。。。。。。。

三、NSNotification Crash

产生的原因:
当一个对象添加了notification之后,如果dealloc的时候,仍然持有notification,就会出现NSNotification类型的crash。NSNotification类型的crash多产生于程序员写代码时候犯疏忽,在NSNotificationCenter添加一个对象为observer之后,忘记了在对象dealloc的时候移除它。

iOS9之前会crash,iOS9之后苹果系统已优化。在iOS9之后,即使开发者没有移除observer,Notification crash也不会再产生了。

解决方案:
NSNotification Crash的防护原理很简单, 利用method swizzling hook NSObject的dealloc函数,再对象真正dealloc之前先调用一下:[[NSNotificationCenter defaultCenter] removeObserver:self],即可。
#import 

/**
当一个对象添加了notification之后,如果dealloc的时候,仍然持有notification,就会出现NSNotification类型的crash。

iOS9之后专门针对于这种情况做了处理,所以在iOS9之后,即使开发者没有移除observer,Notification crash也不会再产生了
*/

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (NSNotificationCrash)

+ (void)xz_enableNotificationProtector;

@end

NS_ASSUME_NONNULL_END
#import "NSObject+NSNotificationCrash.h"
#import "NSObject+XZSwizzle.h"
#import


static const char *isNSNotification = "isNSNotification";

@implementation NSObject (NSNotificationCrash)


+ (void)xz_enableNotificationProtector {

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSObject *objc = [[NSObject alloc] init];

[objc xz_instanceSwizzleMethod:@selector(addObserver:selector:name:object:) replaceMethod:@selector(xz_addObserver:selector:name:object:)];

// 在ARC环境下不能显示的@selector dealloc。
[objc xz_instanceSwizzleMethod:NSSelectorFromString(@"dealloc") replaceMethod:NSSelectorFromString(@"xz_dealloc")];
});
}

- (void)xz_addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject {

// 添加标志位,在delloc中只有isNSNotification是YES,才会移除通知
[observer setIsNSNotification:YES];
[self xz_addObserver:observer selector:aSelector name:aName object:anObject];
}


- (void)setIsNSNotification:(BOOL)yesOrNo {
objc_setAssociatedObject(self, isNSNotification, @(yesOrNo), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)isNSNotification {
NSNumber *number = objc_getAssociatedObject(self, isNSNotification);;
return [number boolValue];
}

/**
如果一个对象从来没有添加过通知,那就不要remove操作
*/
- (void)xz_dealloc
{
if ([self isNSNotification]) {
NSLog(@"CrashProtector: %@ is dealloc,but NSNotificationCenter Also exsit",self);
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

[self xz_dealloc];
}

@end

四、NSTimer Crash 防护

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

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

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

#import 

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 

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


摘自链接:https://www.jianshu.com/p/3324786893a1

0 个评论

要回复文章请先登录注册