这里写目录标题
- 介绍
- 设计模式介绍
- 举例
- iOS 中已有的 观察者设计模式实现
- Notification
- 什么是通知机制或者说如何实现通知机制?
- KVO
- KVO底层实现
- 如何实现手动KVO?
介绍
设计模式介绍
观察者设计模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一种一对多的依赖关系,使得当一个对象的状态发生变化时,所有依赖于它的对象都会受到通知并自动更新。
在这种模式中,被观察者对象(Subject)存储其观察者对象(Observer)列表,并提供了用于添加和删除观察者对象的方法。而观察者对象则实现了一个更新方法来响应被观察者状态的变化。
举例
下面我们用一个简单的 Swift 代码例子来阐述观察者设计模式的应用。
假设我们有一个带有多个温度传感器的系统,并且我们需要监控这些传感器的温度变化。我们可以使用观察者模式来实现:
首先,我们定义一个 Subject 协议来规范被观察者对象的行为:
protocol Subject {
func addObserver(observer: Observer)
func removeObserver(observer: Observer)
func notifyObservers()
}
在这里,我们定义了 addObserver()、removeObserver() 和 notifyObservers() 方法,分别用于添加、删除观察者和通知观察者。
然后,我们定义一个 Observer 协议来规范观察者对象的行为:
protocol Observer {
func update(temperature: Double)
}
在 Observer 中,我们定义了一个 update() 方法,用于响应被观察者状态的变化。
最后,我们定义一个 WeatherStation 对象,实现 Subject 协议。在 WeatherStation 中,我们维护了一个 observers 数组,用于存储所有观察者对象,并在温度变化时调用 notifyObservers() 方法通知所有观察者:
class WeatherStation: Subject {
var temperature: Double = 0
var observers: [Observer] = []
func addObserver(observer: Observer) {
observers.append(observer)
}
func removeObserver(observer: Observer) {
if let index = observers.firstIndex(where: { $0 === observer }) {
observers.remove(at: index)
}
}
func notifyObservers() {
for observer in observers {
observer.update(temperature: temperature)
}
}
func setTemperature(temperature: Double) {
self.temperature = temperature
notifyObservers()
}
}
在 WeatherStation 中,我们使用 addObserver() 方法将观察者对象添加到 observers 数组中,使用 removeObserver() 方法将观察者对象从 observers 数组中删除。而 setTemperature() 方法用于设置温度值,并在温度变化时通知所有观察者。
最后,我们定义一个 Display 作为观察者对象,实现 Observer 协议。在 Display 中,我们实现了 update() 方法,在被观察者状态改变时更新显示:
class Display: Observer {
func update(temperature: Double) {
print("当前温度为:\(temperature)")
}
}
在客户端代码中,我们可以实例化一个 WeatherStation 对象,并添加多个 Display 观察者对象,然后设置温度值,从而触发所有观察者对象的更新:
let weatherStation = WeatherStation()
let display1 = Display()
let display2 = Display()
weatherStation.addObserver(observer: display1)
weatherStation.addObserver(observer: display2)
weatherStation.setTemperature(temperature: 25)
输出结果:
当前温度为:25.0
当前温度为:25.0
在这个例子中,我们使用观察者模式实现了多个观察者对同一个被观察者状态的响应,并使得被观察者状态的变化自动触发所有观察者的更新。
iOS 中已有的 观察者设计模式实现
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。 简而言之,就是A和B,A对B的变化感兴趣,就注册为B的观察者,当B发生变化时通知A,告知B发生了变化。这个也叫做经典观察者模式。
Notification
对于感兴趣的A来说,在这里定义通知,也就是注册观察者(A就是观察者,怎么观察的以及观察到了会做些什么)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notice:) name:@"tongzhi" object:nil];
-(void)notice:(id)sender{
NSLog(@"%@",sender);
}
对于变化源B来说,在B这里发出通知
//创建通知对象
NSNotification *notification = [NSNotification notificationWithName:@"tongzhi" object:nil];
//Name是通知的名称 object是通知的发布者(是谁要发布通知,也就是对象) userInfo是一些额外的信息(通知发布者传递给通知接收者的信息内容,字典格式)
// [NSNotification notificationWithName:@"tongzhi" object:nil userInfo:nil];
//发送通知
[[NSNotificationCenter defaultCenter] postNotification:notification];
当然了,还要移除观察者,在dealloc里面
- (void)dealloc {
//删除根据name和对象,如果object对象设置为nil,则删除所有叫name的,否则便删除对应的
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"tongzhi" object:nil];
}
什么是通知机制或者说如何实现通知机制?
(假如让你实现,你会怎么做)
KVO
KVO全称叫Key Value Observing,顾名思义就是一种观察者模式用于监听属性的变化,KVO和NSNotification有很多相似的地方,用addObserver:forKeyPath:options:context方法 去观察,用removeObserver:forKeyPath:context去移除观察者,用observeValueForKeyPath:ofObject:change:context:去响应观察者
KVO监听属性的变化非常方便,下面举个例子
//自定义MyTimer类,在.h文件中定义一个属性name
@property (nonatomic, strong) NSString *name;
//自定义ViewController,在controller的.h文件中也定义一个属性myView
@property (nonatomic, strong) UIView *myView;
//在Viewcontroller的.m文件中定义一个button,设置点击事件,在该事件中分别调用上面定义的两个属性
int i = 5;
int sum = 15;
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = CGRectMake(100, 100, 100, 30);
[btn setTitle:@"点击" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(handleTimer:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
_label = [[UILabel alloc ] initWithFrame:CGRectMake(100, 200, 180, 30)];
_label.text = @"当前年龄15岁";
[self.view addSubview:_label];
//创建Mytimer对象
_ourTimer = [[MyTimer alloc ] init];
//观察属性name
[_ourTimer addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew || NSKeyValueChangeOldKey context:nil];
//观察属性myView
[self addObserver:self forKeyPath:@"myView" options:NSKeyValueObservingOptionNew || NSKeyValueChangeOldKey context:nil];
}
//点击事件,分别调用属性
- (void)handleTimer:(UIButton *)btn {
_ourTimer.name = @"小明";
self.myView = nil;
NSLog(@"第一次设置名字");
}
//一旦属性被操作了,这里会自动响应(上面设置观察的属性才会在这响应)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"名字发生了改变");
_label.text = [NSString stringWithFormat:@"当前年龄%d岁", i + sum];
sum = i + sum;
} else if ([keyPath isEqualToString:@"myView"]) {
NSLog(@"我的视图");
}
}
//移除
- (void)dealloc {
[_ourTimer removeObserver:self forKeyPath:@"name"];
[self removeObserver:self forKeyPath:@"myView"];
}
KVO底层实现
1、我们调用[self addObserver:self forKeyPath:@“highlighted” options:0 context:nil];
2、系统会动态自定义NSKVONotifying_A子类
3、重写setName,在内部恢复父类做法,通知观察者
4、如何让外界调用自定义A类的子类方法,修改当前对象的isa指针,指向NSKVONotifying_A
个人理解:isa指针改变,那么实例对象的isa指针指向类对象,类对象里面有实例对象的方法,所以isa指针改变,那么就回去调用新类的类对象方法,所以就能够实现调用子类的那个set方法。
重写set方法内部实现:
didChangeValueForKey方法内部会做出通知监听器,某个属性发生了变化。
如何实现手动KVO?
某个类实例对象直接调用这两个方法即可:
手动KVO就是调用了[self.person willChangeValueForKey:@””];
之后又调用了[self.person didChangeValueForKey:@””];
然后didChangeValueForKey内部实现就会触发KVO的回调。