前言:html
NSNotificationCenter 較之於 Delegate 能夠實現更大的跨度的通訊機制,能夠爲兩個無引用關係的兩個對象進行通訊。NSNotification是iOS中一個調度消息通知的類,採用單例模式設計。所以,註冊觀察者後,沒有在觀察者dealloc時及時註銷觀察者,極有可能通知中心再發送通知時發送給殭屍對象而發生crash。ios
蘋果在iOS9以後專門針對於這種狀況作了處理,因此在iOS9以後,即便開發者沒有移除observer,Notification crash也不會再產生了。數組
不過針對於iOS9以前的用戶,咱們仍是有必要作一下NSNotification Crash的防禦。函數
本文從notification的使用狀況、crash狀況進行講解,最後提出了兩種crash防禦的方案:第一種方案是被動防禦,就是crash的代碼可能已經在代碼中了,咱們在底層使用swizzle的方法進行了改進;第二種方案是主動防禦,就是在寫代碼以前,咱們本身寫一套機制,能夠有效的防禦crash的發生。post
1、NSNotification的使用測試
(1) Notification的觀察者類this
//.h文件 extern NSString *const CRMPerformanceNewCellCurrentPageShouldChange; //.m文件 NSString *const CRMPerformanceNewCellCurrentPageShouldChange = @"CRMPerformanceNewCellCurrentPageShouldChange"; //addObserver [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(currentPageShouldChange:) name:CRMPerformanceNewCellCurrentPageShouldChange object:nil]; // 執行函數 - (void)currentPageShouldChange:(NSNotification*)aNotification { NSNumber *number = [aNotification object]; self.pageControl.currentPage = [number integerValue]; } - (void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self]; //或者 [[NSNotificationCenter defaultCenter] removeObserver:self name:aName object:anObject]; }
(2)post Notification類atom
[[NSNotificationCenter defaultCenter] postNotificationName:CRMPerformanceNewCellCurrentPageShouldChange object:@(performanceTabConfigure.tab)];
2、NSNotification的crash狀況spa
「殭屍對象」(出現殭屍對象會報reason=SIGSEGV)設計
在退出A頁面的時候沒有把自身的通知觀察者A給註銷,致使通知發過來的時候拋給了一個已經釋放的對象A,但該對象仍然被通知中心引用,也就是殭屍對象,從而致使程序崩潰 。(也就是說,在一個頁面dealloc的時候,必定要把這個頁面在通知中心remove掉,不然這個頁面頗有可能成爲殭屍對象)。
但有兩種狀況須要注意:
(1)單例裏不用dealloc方法,應用會統一管理;
(2)類別裏不要用dealloc方法removeObserver,在類別對應的原始類裏的dealloc方法removeObserver,由於類別會調用原始類的dealloc方法。(若是在類別裏新寫dealloc方法,原類裏的dealloc方法就不執行了)。
3、crash 防禦方案
方案1、
利用method swizzling hook NSObject的dealloc函數,在對象真正dealloc以前先調用一下[[NSNotificationCenter defaultCenter] removeObserver:self]便可。
注意到並非全部的對象都須要作以上的操做,若是一個對象歷來沒有被NSNotificationCenter 添加爲observer的話,在其dealloc以前調用removeObserver徹底是畫蛇添足。 因此咱們hook了NSNotificationCenter的 addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject。函數,在其添加observer的時候,對observer動態添加標記flag。這樣在observer dealloc的時候,就能夠經過flag標記來判斷其是否有必要調用removeObserver函數了。
//NSNotificationCenter+CrashGuard.m #import "NSNotificationCenter+CrashGuard.h" #import <objc/runtime.h> #import <UIKit/UIDevice.h> #import "NSObject+NotificationCrashGuard.h" @implementation NSNotificationCenter (CrashGuard) + (void)load{ if([[[UIDevice currentDevice] systemVersion] floatValue] < 9.0) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [[self class] swizzedMethod:sel_getUid("addObserver:selector:name:object:") withMethod:@selector(crashGuard_addObserver:selector:name:object:)]; }); } } +(void)swizzedMethod:(SEL)originalSelector withMethod:(SEL )swizzledSelector { Class class = [self class]; Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else{ method_exchangeImplementations(originalMethod, swizzledMethod); } } -(void)crashGuard_addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject { NSObject *obj = (NSObject *)observer; obj.notificationCrashGuardTag = notificationObserverTag; [self crashGuard_addObserver:observer selector:aSelector name:aName object:anObject]; } @end // NSObject+NotificationCrashGuard.h #import <Foundation/Foundation.h> extern NSInteger notificationObserverTag; @interface NSObject (NotificationCrashGuard) @property(nonatomic, assign)NSInteger notificationCrashGuardTag; @end // NSObject+NotificationCrashGuard.m #import "NSObject+NotificationCrashGuard.h" #import "NSObject+Swizzle.h" #import <UIKit/UIDevice.h> #import <objc/runtime.h> NSInteger notificationObserverTag = 11118; @implementation NSObject (NotificationCrashGuard) #pragma mark Class Method + (void)load{ if([[[UIDevice currentDevice] systemVersion] floatValue] < 9.0) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [[self class] swizzedMethod:sel_getUid("dealloc") withMethod:@selector(crashGuard_dealloc)]; }); } } #pragma Setter & Getter -(NSInteger)notificationCrashGuardTag { NSNumber *number = objc_getAssociatedObject(self, _cmd); return [number integerValue]; } -(void)setNotificationCrashGuardTag:(NSInteger)notificationCrashGuardTag { NSNumber *number = [NSNumber numberWithInteger:notificationCrashGuardTag]; objc_setAssociatedObject(self, @selector(notificationCrashGuardTag), number, OBJC_ASSOCIATION_RETAIN); } -(void)crashGuard_dealloc { if(self.notificationCrashGuardTag == notificationObserverTag) { [[NSNotificationCenter defaultCenter] removeObserver:self]; } [self crashGuard_dealloc]; }
此方案有如下缺點:
(1)ARC開發下,dealloc做爲關鍵字,編譯器是有所限制的。會產生編譯錯誤「ARC forbids use of 'dealloc' in a @selector」。不過咱們能夠用運行時的方式進行解決。
(2)dealloc做爲最爲基礎,調用次數最爲頻繁的方法之一。如對此方法進行替換,一是代碼的引入對工程影響範圍太大,二是執行的代價較大。由於大多數dealloc操做是不須要引入自動註銷的,爲了少數需求而對全部的執行都作修正是不適當的。
方案2、
基於以上的分析,Method Swizzling能夠做爲最後的備選方案,但不適合做爲首選方案。
另一個思路是在宿主釋放過程當中嵌入咱們本身的對象,使得宿主釋放時順帶將咱們的對象一塊兒釋放掉,從而獲取dealloc的時機點。顯然AssociatedObject是咱們想要的方案。相比Method Swizzling方案,AssociatedObject方案的對工程的影響範圍小,並且只有使用自動註銷的對象纔會產生代價。
鑑於以上對比,因而採用構建一個釋放通知對象,經過AssociatedObject方式鏈接到宿主對象,在宿主釋放時進行回調,完成註銷動做。
首先,看一下OC對象銷燬時的處理過程,以下objc_destructInstance函數:
/****************** * objc_destructInstance * Destroys an Instance without freeing memory. * Calls C++ destructors. * Calls ARR ivar cleanup. * Remove associative references. * Returns 'obj'. Does nothing if 'obj' is nil. * Be warned that GC DOES NOT CALL THIS. if you edit this, also edit finalize. * CoreFoundation and other clients do call this under GC. ******************/ void *objc_destructInstance(id obj){ if(obj){ bool cxx = obj->hasCxxDtor(); bool assoc = !UseGC && obj->hasAssociatedObject(); bool dealloc = !UseGC; if(cxx) object_cxxDestruct(obj); if(assoc) _object_remove_assocations(obj); if(dealloc) obj->clearDeallocating(); } return obj; }
objc_destructInstance函數中:(1)object_cxxDestruct負責遍歷持有的對象,並進行析構銷燬。(2)_object_remove_assocations負責銷燬關聯對象。(3)clearDeallocating清空引用計數表並清除弱引用表, 並負責對weak持有本對象的引用置nil(Weak表是一個hash表,而後裏面的key是指向對象的地址,Value是Weak指針的地址的數組)。(附《ARC下dealloc過程》《iOS 底層解析weak的實現原理》)
根據上面的分析,咱們對通知添加觀察時,能夠爲觀察者動態添加一個associate Object,由這個associate Object進行添加觀察操做,在觀察者銷燬時,associate Object會自動銷燬,咱們在associate Object的銷燬動做中,自動remove掉觀察者。
具體實現以下:
(1)咱們建立一個NSObject的分類NSObject+AdNotifyEvent。在這個Category中,咱們建立了添加觀察者的方法,其具體實現由它的associate Object實現。這裏的associate Object是類SLVObserverAssociater的對象。
// NSObject+AdNotifyEvent.h #import <Foundation/Foundation.h> #import "SLVObserverAssociater.h" @interface NSObject (AdNotifyEvent) - (void)slvWatchObject:(id)object eventName:(NSString *)event block:(SLVNotifyBlock)block; - (void)slvWatchObject:(id)object eventName:(NSString *)event level:(double)level block:(SLVNotifyBlock)block; @end // NSObject+AdNotifyEvent.m #import "NSObject+AdNotifyEvent.h" #import <objc/runtime.h> #import <objc/message.h> @implementation NSObject (AdNotifyEvent) - (SLVObserverAssociater *)observerAssociater { SLVObserverAssociater *observerAssociater = (SLVObserverAssociater *)objc_getAssociatedObject(self, _cmd); if (observerAssociater == nil) { observerAssociater = [[SLVObserverAssociater alloc] initWithObserverObject:self]; objc_setAssociatedObject(self, _cmd, observerAssociater, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return observerAssociater; } - (void)slvWatchObject:(id)object eventName:(NSString *)event block:(SLVNotifyBlock)block { [[self observerAssociater] addNotifyEvent:event watchObject:object observerObject:self level:RFEventLevelDefault block:block]; } - (void)slvWatchObject:(id)object eventName:(NSString *)event level:(double)level block:(SLVNotifyBlock)block { [[self observerAssociater] addNotifyEvent:event watchObject:object observerObject:self level:level block:block]; } @end
(2)SLVObserverAssociater的實現
頭文件:
// SLVObserverAssociater.h #import <Foundation/Foundation.h> #import "SLVNotifyLevelBlocks.h" @interface SLVObserverAssociater : NSObject @property (nonatomic, weak) id observerObject; // selfRef,觀察者 @property (nonatomic, strong) NSMutableDictionary *notifyMap; // key:通知名_watchObject value:RFNotifyEventObject - (id)initWithObserverObject:(id)observerObject; - (void)addNotifyEvent:(NSString *)event watchObject:(id)watchObject observerObject:(id)observerObject level:(double)level block:(SLVNotifyBlock)block; @end @interface SLVNotifyInfo : NSObject @property (nonatomic, weak) SLVObserverAssociater *associater; @property (nonatomic, unsafe_unretained) id watchObject; // 被觀察對象 @property (nonatomic, strong) NSString *event; @property (nonatomic, strong) NSMutableArray *eventInfos; @property (nonatomic, weak) id sysObserverObj; // 觀察者 - (id)initWithRFEvent:(SLVObserverAssociater *)rfEvent event:(NSString *)event watchObject:(id)watchObject; - (void)add:(SLVNotifyLevelBlocks *)info; - (void)removeLevel:(double)level; - (void)handleRFEventBlockCallback:(NSNotification *)note; @end
實現文件,分三部分:(a)初始化方法(b)添加觀察的方法 (c)dealloc時,註銷觀察的方法
// SLVObserverAssociater.m #import "SLVObserverAssociater.h" #pragma mark - SLVObserverAssociater @implementation SLVObserverAssociater #pragma mark Init - (id)initWithObserverObject:(id)observerObject { self = [super init]; if (self) { _notifyMap = [NSMutableDictionary dictionary]; _observerObject = observerObject; } return self; } #pragma mark Add Notify - (void)addNotifyEvent:(NSString *)event watchObject:(id)watchObject observerObject:(id)observerObject level:(double)level block:(SLVNotifyBlock)block { NSString *key = [NSString stringWithFormat:@"%@_%p", event, watchObject]; SLVNotifyInfo *ne = [self.notifyMap objectForKey:key]; if (ne == nil) { // 添加監聽 ne = [[SLVNotifyInfo alloc] initWithRFEvent:self event:event watchObject:watchObject]; [self addNotifyEvent:ne forKey:key]; } SLVNotifyLevelBlocks *nei = [[SLVNotifyLevelBlocks alloc] init]; nei.level = level; nei.block = block; [ne add:nei]; } - (void)addNotifyEvent:(SLVNotifyInfo *)ne forKey:(NSString *)key { self.notifyMap[key] = ne; __weak SLVNotifyInfo *neRef = ne; ne.sysObserverObj = [[NSNotificationCenter defaultCenter] addObserverForName:ne.event object:ne.watchObject queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note){ [neRef handleRFEventBlockCallback:note]; }]; } #pragma mark Remove Observer - (void)dealloc { [self removeNotifyEvent:nil watchObject:nil ignoreLevel:YES level:0]; } - (void)removeNotifyEvent:(NSString *)event watchObject:(id)watchObject ignoreLevel:(BOOL)bIgnoreLevel level:(double)level { if (event == nil && watchObject == nil) { // 移除掉全部 NSArray *keys = [self.notifyMap allKeys]; for (NSString *key in keys) { [self removeNotifyEventKey:key]; } } else if (event != nil && watchObject == nil) { NSArray *keys = [self.notifyMap allKeys]; for (NSString *key in keys) { SLVNotifyInfo *ne = self.notifyMap[key]; if ([ne.event isEqualToString:event]) { [self removeNotifyEventKey:key]; } } } else if (event == nil && watchObject != nil) { NSArray *keys = [self.notifyMap allKeys]; for (NSString *key in keys) { SLVNotifyInfo *ne = self.notifyMap[key]; if (ne.watchObject == watchObject) { [self removeNotifyEventKey:key]; } } } else { NSArray *keys = [self.notifyMap allKeys]; for (NSString *key in keys) { SLVNotifyInfo *ne = self.notifyMap[key]; if ([ne.event isEqualToString:event] && ne.watchObject == watchObject) { if (bIgnoreLevel) { [self removeNotifyEventKey:key]; } else { [ne removeLevel:level]; if (ne.eventInfos.count == 0) { [self removeNotifyEventKey:key]; } } break; } } } } - (void)removeNotifyEventKey:(NSString *)key { SLVNotifyInfo *ne = self.notifyMap[key]; if (ne.sysObserverObj != nil) { [[NSNotificationCenter defaultCenter] removeObserver:ne.sysObserverObj]; ne.sysObserverObj = nil; } [self.notifyMap removeObjectForKey:key]; } @end #pragma mark - SLVNotifyInfo @implementation SLVNotifyInfo - (id)initWithRFEvent:(SLVObserverAssociater *)associater event:(NSString *)event watchObject:(id)watchObject { self = [super init]; if (self) { _associater = associater; _event = event; _watchObject = watchObject; _eventInfos = [NSMutableArray array]; } return self; } - (void)dealloc { _watchObject = nil; } - (void)add:(SLVNotifyLevelBlocks *)info { BOOL bAdd = NO; for (NSInteger i = 0; i < self.eventInfos.count; i++) { SLVNotifyLevelBlocks *eoi = self.eventInfos[i]; if (eoi.level == info.level) { [self.eventInfos replaceObjectAtIndex:i withObject:info]; bAdd = YES; break; } else if (eoi.level > info.level) { [self.eventInfos insertObject:info atIndex:i]; bAdd = YES; break; } } if (!bAdd) { [self.eventInfos addObject:info]; } } // 按lever從小到大添加block - (void)removeLevel:(double)level { for (NSInteger i = 0; i < self.eventInfos.count; i++) { SLVNotifyLevelBlocks *eoi = self.eventInfos[i]; if (eoi.level == level) { [self.eventInfos removeObjectAtIndex:i]; break; } } } - (void)handleRFEventBlockCallback:(NSNotification *)note { for (NSInteger i = self.eventInfos.count-1; i >= 0; i--) { SLVNotifyLevelBlocks *nei = self.eventInfos[i]; SLVNotifyBlock block = nei.block; if (block != nil) { block(note, self.associater.observerObject); } } } // 按順序執行block,block是響應通知時候的內容
另外,爲通知的回調block排了優先級:
#define RFEventLevelDefault 1000.0f typedef void (^SLVNotifyBlock) (NSNotification *note, id selfRef); @interface SLVNotifyLevelBlocks : NSObject @property (nonatomic, assign) double level; // block的優先級 @property (nonatomic, copy) SLVNotifyBlock block; //收到通知後的回調block @end
測試代碼:
- (void)viewDidLoad { [super viewDidLoad]; [self slvWatchObject:nil eventName:@"lvNotification" block:^(NSNotification *aNotification, id weakSelf){ NSLog(@"收到一個通知,如今開始處理了。。。"); } ]; }
這樣,咱們在註冊通知的觀察者時,使用咱們的分類NSObject+AdNotifyEvent中的註冊觀察方法就能夠了,在類銷燬時,就會自動在通知中心remove掉觀察者了。
總結:
本文從notification的使用狀況、crash狀況進行講解,最後提出了兩種crash防禦的方案:第一種方案是被動防禦,就是crash的代碼可能已經在代碼中了,咱們在底層使用swizzle的方法進行了改進;第二種方案是主動防禦,就是在寫代碼以前,咱們本身寫一套機制,能夠有效的防禦crash的發生。根據上面的分析,比較推薦第二種方案。本文的第二種方案參考自《iOS釋放自注銷模式設計》。