NSNotification与类对象,实例对象

问题产生

一般我们在实例方法中通过设置对NSNotification的监听,然后用实例方法来处理消息。

那么我们是否可以在类方法中监听通知 ,是否可以使用类方法来处理通知 ?

验证步骤

初始化一个parents类,并发送一个名为study的通知

parents类

- (void) postNotification {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"study" object:nil];
}
复制代码

情况一:类方法中监听,类方法中处理

student类

+ (void) addListener {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hanleNotification:) name:@"study" object:nil];
}

+ (void) hanleNotification:(NSNotification*) notification {
    NSLog(@"该学习啦")
}

- (void) dealloc {
    NSLog(@"对象销毁");
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

复制代码

在student类的类方法中设置对名为study的通知监听,并设置通知处理方法为类方法

+ (void) hanleNotification:(NSNotification*) notification
复制代码

student类中可以收到通知,并打印输出。
并且之后不管实例化多少个student对象,销毁多少个student对象,类方法中的监听依旧存在,也依旧能够处理通知。

情况二: 类方法中监听,实例方法处理

+ (void) addListener {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hanleNotification:) name:@"study" object:nil];
}

- (void) hanleNotification:(NSNotification*) notification {
    NSLog(@"该学习啦")
}

- (void) dealloc {
    NSLog(@"对象销毁");
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
复制代码

在发送通知之后,student中能收到通知,但是找不到处理方法。

情况三 : 实例方法中监听,类方法中处理

- (void) addListener {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hanleNotification:) name:@"study" object:nil];
}

+ (void) hanleNotification:(NSNotification*) notification {
    NSLog(@"该学习啦")
}

- (void) dealloc {
    NSLog(@"对象销毁");
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
复制代码

在parents中实例化一个student对象,用来调用实例方法监听通知。

- (void) postNotification {
    //实例化一个Student对象,使用实例方法监听通知
    Student stu = [[Student alloc] init];
    [stu addListener];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"study" object:nil];
}
复制代码

当parents中发送通知之后,实例对象stu能够收到通知,但是找不到方法执行

原理解析

方法的调用

在OC中,方法的调用其实就是给对象发消息,如果在该对象的本类中找不到处理方法,就会沿着继承链向上去找到父类,直至找到根类,如果根类中也找不到处理方法就会进行消息转发,如果没有能处理消息的对象就会报错,消息处理方法找不到。

实例对象和类对象的方法保存

在OC中,实例对象其实是根据isa指针,由该实例对象所对应的类构造出来的,所以实例对象中的实例方法保存在其类中,有且仅有一份。实例对象本身是不保存方法的。

同理,类本身也是一个对象,也是根据自身的isa指针,找到其所对应的元类,由元类所构造出来的。所以类方法也就保存在元类当中,所以类对象本身不保存类方法。

NSNotificationCenter 发送通知原理

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hanleNotification:) name:@"study" object:nil];
复制代码

[NSNotificationCenter defaultCenter]是一个全局单例,在添加监听的时候,根据传入的name,分别生成不同的回调队列(其实就是一个map,下面会讲具体原理)

如果是在实例方法中注册的监听,那么这个注册的self,代表观察者为实例对象。

如果是在类方法中注册的监听,那么这个注册的self,代表观察者为类对象

[[NSNotificationCenter defaultCenter] postNotificationName:@"study" object:nil];
复制代码

在发送通知的时候,其实就是根据name属性,找到对应的回调队列,向回调队列里面的注册观察者对象,发送该注册对象处理通知方法的消息,也就是selector:@selector(hanleNotification:) 里设置的处理方法。

NamedTable和 UnNamed Table 通知中心NSNotificationCenter

通知中心NSNotificationCenter,根据name保存观察者的具体实现。

NSNotificationCenter存储观察者的结构体

typedef struct NCTbl {
  Observation   *wildcard;  /* 保存既没有没有传入通知名字也没有传入object的通知*/
 MapTable       nameless;   /*保存没有传入通知名字的通知 */
 MapTable       named; /*保存传入了通知名字的通知 */
} NCTable;
复制代码

NamedTable代表传入name的通知

UnNamed Table代表没有name的通知,这里不详细讲解

NamedTable

NamedTable保存数据具体形式

image.png

首先整个namedTable是一个Map,每个通知的名称作为key,比如上例中的study就是一个通知名称,也就是一个key.其对应的value,同样是一个Map,其key为通知的object。也就是下面代码中的School。

NSNotification notificationWithName:"study" object:School];
复制代码

每个object对应value是一个链表。其实现形式如下。根据oc中方法调用原理可以知道,方法的调用其实就是给对象发消息,这里对象有了就是observer,方法也有了selector,就可以调用方法objc_msgSend,传入对象和方法,向对象发送一个调用消息。

typedef struct  Obs {
  id        observer;   /* 保存接受消息的对象*/
  SEL       selector;   /* 保存注册通知时传入的SEL*/
  struct Obs    *next;      /* 保存注册了同一个通知的下一个观察者*/
  struct NCTbl  *link;  /* 保存改Observation的Table*/
} Observation;
复制代码

所以如果对象释放的时候,没有及时移除自身在回调队列里面的注册,那么在新收到通知触发回调队列之后,回调队列会向一个被释放的对象发送方法调用消息,导致异常。

所以在注册监听对象被释放掉的时候,需要及时移除在通知回调队列里面的注册

- (void) dealloc {
    NSLog(@"对象销毁");
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
复制代码

在iOS 9.0以上的版本中,通知中心持有的观察者。由unsafe_unretained引用变为,weak引用,即使观察者对象,在释放的时候没有被移除,也因为通知中心由于是弱引用的方式,在观察者被回收之后,持有观察者的弱引用也会被移除。addObserverForName:object:queue:usingBlock:由于还是强引用,所以还是需要手动释放

情况解析

根据上面原理可以得出如下,
image.png

针对情况一:类方法中监听,类方法中处理

因为观察者注册的是类对象,所以使用类方法来处理通知消息是可以的,因为向类对象发送方法调用消息,实际上就是去其元类里面找实现方法,由于类方法也在元类里面。所以可以找到完成调用。

不过由于类对象在第一次被import的时候就创建了,而且在整个程序生命周期中都存在,所以该注册的观察者在整个程序生命周期中都会接收处理通知消息。

针对情况二,情况三

不管观察者注册的是类对象还是实例对象,都需要指定自身构造类中的方法来完成消息的处理,如果类对象中使用实例方法来处理,方法就找不到,同样实例对象中使用类方法来处理也找不到。

总结

在注册观察者的时候,需注意设置的观察对象(addObserver)和处理方法(@selector),确保在该对象能够找到该方法,由于处理方法是运行时才会去调用,所以静态分析并不会提示错误,需要格外小心!

如果文中有什么地方有问题的话,欢迎大家在评论中指出,我会及时更正。Thanks!!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享