监测那些常规方法捕捉不到的变化-观察者模式

一、你可能遇到过的问题

Question 1.使用UITextField时;

当你想时时监测textField.text的变化,以获取最新的text,你是用这样的代理方法吗?

1
2
3
4
5
6
7
8
9
10
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

if (textField == self.textField) {
NSString *futureStr = [NSString stringWithFormat:@"%@%@", textField.text, string];
if ([futureStr isEqualToString:@"000"]) {
NSLog(@"----------The text value is 000----------");
}
}
return YES;
}

Question 2.使用UIWebView或WKWebView时;

由于网页端用Ajax,导致我们并不能用常规的网页加载代理方法来检测webView的title的值~比如这样

1
2
3
4
5
6
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {

if (webView.title.length > 0) {
self.title = webView.title;
}
}

二、这里我们就需要用上观察者模式了

KVO,即key-value-observing,利用一个key来找到某个属性并监听其值得改变。其实这也是一种典型的观察者模式。

  • 1,添加观察者
  • 2,在观察者中实现监听方法,observeValueForKeyPath: ofObject: change: context:(因为这个方法属于NSObject,所以,可以用于检测任何对象的任何属性的变化)
  • 3,移除观察者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
用万能的User类来煮个栗子~
*/
- (void)addObserverForUserName {

User *user = [[User alloc] init];

/* Whether the change dictionaries sent in notifications
should contain NSKeyValueChangeNewKey and NSKeyValueChangeOldKey entries, respectively.
-------------------------------------------------------------
NSKeyValueObservingOptionNew = 0x01,新值
NSKeyValueObservingOptionOld = 0x02,旧值
*/
[user addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
}

/**
监测 user 的 name 属性 发生变化的情况

@param keyPath 被监听的属性
@param object 被监听的对象
@param change 新值和旧值
@param context 附加的额外数据
*/
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context {

if ([keyPath isEqualToString:@"name"] && [object isEqual:user]) {
// Do Something
NSLog(@"----------user'name has changed----------");
}
}

- (void)dealloc {

[user removeObserver:self forKeyPath:@"name"];
}

三、解决上述及类似的问题

Answer 1

1
2
3
4
5
6
7
8
9
[self.textField addTarget:self action:@selector(textFieldDidChange:) 
forControlEvents:UIControlEventEditingChanged];

- (void)textFieldDidChange:(UITextField *)textField {
// TODO
if ([textField.text isEqualToString:@"000"]) {
NSLog(@"----------The text value is 000----------");
}
}

Answer 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
为webView配置监听者
*/
- (void)configWebViewObserver {

[webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
[webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context {

if ([keyPath isEqualToString:@"title"] && [object isEqual:webView]) {
self.title = self.webView.title;
} else if ([keyPath isEqualToString:@"estimatedProgress"] && [object isEqual:webView]) {
self.progressView.progress = self.webView.estimatedProgress;
}
}

- (void)dealloc {

[self.webView removeObserver:self forKeyPath:@"title"];
[self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
}

附:再来一发监听UITableView的contentOffset

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)configObserverForTableView {

[tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {

if ([keyPath isEqualToString:@"contentOffset"] && object == tableView) {

CGFloat offsetY = self.contentOffset.y;
CGFloat offsetX = self.contentOffset.x;
NSLog(@"----------offsetX = %f, offsetY = %f----------", offsetX, offsetY);
}
}

- (void)dealloc {

[tableView removeObserver:self forKeyPath:@"contentOffset"];
}

看到这里大家一定就能明白观察者模式的基本套路了吧,心里突然响起了音乐:”其实很简单,其实很自然”~

四、写在最后

  • 此方法可用于监听一切对象的属性变化,包括系统的类(UITableView、UIWebView等)和自定义的类(User、Car等)
  • 上面的User类是通例,属于基本模型,实际情况有需求而定
  • 还可以用于监听动画的特殊时刻,比如:frame、color等变化
  • 一定记得在析构函数里移除监听;
  • 一定记得在析构函数里移除监听;
  • 一定记得在析构函数里移除监听……

我是与文无关的图.JPG