这篇文件将介绍iOS中的观察者模式,iOS中对于观察者模式的应用有两种,一种是通知,一种是KVO(键值观察)

  • 通知
  • KVO
  • KVO与代理、block(闭包)的区别

观察者模式,处理对象间一对多的关系,当对象相应的信息发生改变时,其关联的其他对象(对其监听的对象)皆收到消息并处理相关操作。

iOS中典型的观察者模式实现方式是:通知(NSNotificationCenter)和KVO。

1 通知

通知,是由通知中心(NSNotificationCenter)进行管理的。通知,有通知的发出者(被观察者),有通知的接收者(观察者)。以下是通知的具体步骤:

1)观察者先通过addObserverAPI向通知中心(NSNotificationCenter)注册监听某一信息(指定通知的name),通知中心会将其放入观察者列表中(猜测是name: observerList);

2)被观察者在指定被观察的位置通过postNotificationName:object:userInfoAPI向通知中心发送指定name的通知

3)通知中心在收到通知后,通过name找出之前注册的观察者,调用其注册时关联的方法或block(回调)。

4)注意:在观察者被销毁的时候要向通知中心注销其观察者,如果不注销,在被观察者发送通知给观察者时,会crash。对象已经被销毁,这时候再那指向对象的指针去调用方法,就会出行野指针bug,crash。

2 KVO

KVO的全称是Key-Value Observer,即键值观察,是一种没有中心枢纽的观察者模式的实现方式(这是其有别于通知的地方)。一个主题对象管理所有依赖于它的观察者对象,并且在自身状态发生改变的时候主动通知观察者对象。

我们在使用时,一是添加观察者调用添加观察者API,另一个是实现观察回调方法以完成监听。但是其具体是如何实现的呢?以下将进行说明。

在介绍前先带大家看一段代码

  1. @interface KKPerson : NSObject
  2. @property (assign, nonatomic) int age;
  3. @end
  4. @interface ViewController ()
  5. @property (strong, nonatomic) KKPerson *person1;
  6. @property (strong, nonatomic) KKPerson *person2;
  7. @end
  8. - (void)viewDidLoad {
  9. [super viewDidLoad];
  10. self.person1 = [[KKPerson alloc] init];
  11. self.person1.age = 1;
  12. self.person2 = [[KKPerson alloc] init];
  13. self.person2.age = 2;
  14. // 给person1对象添加KVO监听
  15. NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
  16. [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
  17. // 打印方法地址
  18. NSLog(@"person1添加KVO监听之后 - %p %p",
  19. [self.person1 methodForSelector:@selector(setAge:)],
  20. [self.person2 methodForSelector:@selector(setAge:)]);
  21. // 打印类对象
  22. NSLog(@"person1添加KVO监听之后 类对象 - %@ %@",
  23. object_getClass(self.person1), // self.person1.isa
  24. object_getClass(self.person2)); // self.person2.isa
  25. }
  26. // 输出内容:
  27. :person1添加KVO监听之前 - 0x103542590 0x103542590
  28. :person1添加KVO监听之前 - KKPerson KKPerson
  29. :person1添加KVO监听之后 - 0x10fc16cf2 0x10f8bd5f0
  30. :person1添加KVO监听之后 类对象 - NSKVONotifying_KKPerson KKPerson

通过可以发现,在给person1对象添加KVO监听后,setAge:的方法地址不再一样,person1的类名也不再一样。如果这里直接打印person1的class,会发现其返还的还是KKPerson。这些基本上能看出一些关于对象被添加KVO监听后的一些细微变化,更复杂或更深层的变化逻辑需要进一步使用反汇编去解读,这里就不再具体展现。

在这里给总结下对象被KVO添加监听后的底层用runtime做了哪些事:

1)利用runtime API动态生成一个子类,并让实例对象的isa指针,从原来的类指向这个子类

2)这个子类会重写监听属性的set方法,在set方法中完成对属性修改的监听,调用监听的方法

3)这个子类的superclass属性指向其父类,方便调用不需要重写的方法,比如属性的get方法

当对象被监听的属性发生改变时的流程:

1)修改实例对象的属性值时,会通过isa指针找到新的子类

2)在新的子类中找到其对应的属性set方法

3)在set方法中会调用Foundation的c语言函数(_NSSetXXXValueAndNotify),这个函数会实现属性的赋值、监听器的触发

4)_NSSetXXXValueAndNotify()函数中会调用:willChangeValueForKey:方法

5)调用父类的set方法,让其属性完成赋值

6)再调用didChangeValueForKey:方法,这个方法内部会触发监听器的监听方法

另外:

1)会重写class方法:返回原始类,让开发者忽略子类的存在,隐藏kvo的内部实现

2)会重写delloc方法:需要在销毁时做些额外处理

3)会重新_isKVOA方法

KVO的其他补充

1)手动触发KVO:手动调用willChangeValueForKey:方法,didChangeValueForKey:方法

2)直接修改成员变量会不会触发KVO:不会触发,因为没有调用其属性的set方法

3 KVO、通知、代理、block的区别

3.1 作用:

简单来说,就是处理对象间的通信。

3.2 对象关系:

KVO和通知,即观察者模式,主要处理一对多的关系,当然也包含一对一的关系;

代理和block主要处理一对一的关系,处理不了一对多的关系。

3.3 对象依赖:

通知,处理的对象间不需要有直接的关联,和依赖;

而KVO和代理及block处理的对象间需要有直接的依赖关系。

3.4 使用建议:

在一对一的情况下,建议使用代理或block,相反在一对多的情况下使用KVO或者通知

在没有关联的对象间通信,建议使用通知,有关联的时候使用KVO或者代理、block。