zoukankan      html  css  js  c++  java
  • [crash详解与防护] NSNotification crash

    前言:

      NSNotificationCenter 较之于 Delegate 可以实现更大的跨度的通信机制,可以为两个无引用关系的两个对象进行通信。NSNotification是iOS中一个调度消息通知的类,采用单例模式设计。因此,注册观察者后,没有在观察者dealloc时及时注销观察者,极有可能通知中心再发送通知时发送给僵尸对象而发生crash。

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

    不过针对于iOS9之前的用户,我们还是有必要做一下NSNotification Crash的防护。

      本文从notification的使用情况、crash情况进行讲解,最后提出了两种crash防护的方案:第一种方案是被动防护,就是crash的代码可能已经在代码中了,我们在底层使用swizzle的方法进行了改进;第二种方案是主动防护,就是在写代码之前,我们自己写一套机制,可以有效的防护crash的发生。

    一、NSNotification的使用

    (1) Notification的观察者类

    //.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类

    [[NSNotificationCenter defaultCenter] postNotificationName:CRMPerformanceNewCellCurrentPageShouldChange object:@(performanceTabConfigure.tab)];

    二、NSNotification的crash情况

    “僵尸对象”(出现僵尸对象会报reason=SIGSEGV)

    在退出A页面的时候没有把自身的通知观察者A给注销,导致通知发过来的时候抛给了一个已经释放的对象A,但该对象仍然被通知中心引用,也就是僵尸对象,从而导致程序崩溃 。(也就是说,在一个页面dealloc的时候,一定要把这个页面在通知中心remove掉,否则这个页面很有可能成为僵尸对象)。

    但有两种情况需要注意:

    (1)单例里不用dealloc方法,应用会统一管理;

    (2)类别里不要用dealloc方法removeObserver,在类别对应的原始类里的dealloc方法removeObserver,因为类别会调用原始类的dealloc方法。(如果在类别里新写dealloc方法,原类里的dealloc方法就不执行了)。

     三、crash 防护方案  

      方案一、

      利用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操作是不需要引入自动注销的,为了少数需求而对所有的执行都做修正是不适当的。

     方案二、

      基于以上的分析,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释放自注销模式设计》。

     
     
  • 相关阅读:
    WebConfig配置文件详解
    python标准库介绍——32 Queue 模块详解
    python标准库介绍——31 threading 模块详解
    python标准库介绍——30 code 模块详解
    python标准库介绍——29 zlib 模块详解
    python标准库介绍——28 sha 模块详解
    python标准库介绍——28 md5 模块详解
    python标准库介绍——27 random 模块详解
    python标准库介绍——26 getopt 模块详解
    python标准库介绍——25 errno 模块详解
  • 原文地址:https://www.cnblogs.com/Xylophone/p/6394056.html
Copyright © 2011-2022 走看看