Crash 防禦方案(六):NSNotificationCenter

原文 : 與佳期的我的博客(gonghonglou.com)git

If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method. Otherwise, you should call this method or removeObserver:name:object: before observer or any object specified in addObserverForName:object:queue:usingBlock: or addObserver:selector:name:object: is deallocated. You shouldn't use this method to remove all observers from a long-lived object, because your code may not be the only code adding observers that involve the object. The following example illustrates how to unregister someObserver for all notifications for which it had previously registered. This is safe to do in the dealloc method, but should not otherwise be used (use removeObserver:name:object: instead).github

這是蘋果官方文檔的說法,也就是說 iOS9 以前,當一個對象添加了 notification 以後,若是 dealloc 的時候,仍然持有 notification,就會出現 NSNotification 類型的 Crash。bash

防禦方案很簡單就是 Hook NSObject 的 dealloc 方法,在對象真正 dealloc 以前先調用一下移除操做:app

[[NSNotificationCenter defaultCenter] removeObserver:self];
複製代碼

固然,爲了避免必要的操做,咱們應該只在添加了通知的對象裏去執行移除。一樣的,Hook NSNotificationCenter 的 - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject; 方法,添加一個標記,在 dealloc 方法里根據這個標記來執行移除操做。this

代碼實現,NSNotificationCenter+GHLCrashGuard 分類裏添加標記:spa

@implementation NSNotificationCenter (GHLCrashGuard)

+ (void)load {
    if ([UIDevice currentDevice].systemVersion.doubleValue < 9.0) {
        [self jr_swizzleMethod:@selector(addObserver:selector:name:object:) withMethod:@selector(ghl_addObserver:selector:name:object:) error:nil];
    }
}

- (void)ghl_addObserver:(id)observer selector:(SEL)aSelector name:(NSNotificationName)aName object:(id)anObject {
    [self ghl_addObserver:observer selector:aSelector name:aName object:anObject];
    
    objc_setAssociatedObject(observer, "addObserverFlag", @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
複製代碼

NSObject+GHLCrashGuard 分類裏 Hook dealloc 方法:code

+ (void)load {
    [self jr_swizzleMethod:NSSelectorFromString(@"dealloc") withMethod:@selector(ghl_dealloc) error:nil];
}

- (void)ghl_dealloc {
        
    if ([UIDevice currentDevice].systemVersion.doubleValue < 9.0) {
        [[GHLNotificationCenterManager sharedInstance] handleObjectRemoveObserver:self];
    }
    [self ghl_dealloc];
}

複製代碼

GHLNotificationCenterManager 的 handleObjectRemoveObserver: 方法實現:server

- (void)handleObjectRemoveObserver:(__unsafe_unretained id)object {
    NSString *addObserver = objc_getAssociatedObject(object, "addObserverFlag");
    if ([addObserver boolValue]) {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
}
複製代碼

Demo 地址:GHLCrashGuard:GHLCrashGuard/Classes/NSNotificationCenter對象

後記

相關文章
相關標籤/搜索