什么是KVO?
KVO
全称Key Value Observing
,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO
的实现机制,只针对属性才会发生作用,一般继承自NSObject
的对象都默认支持KVO
。
KVO
可以监听单个属性的变化,也可以监听集合对象的变化。通过KVC
的mutableArrayValueForKey:
等方法获得代理对象,当代理对象的内部对象发生改变时,会回调KVO
监听的方法。集合对象包含NSArray
和NSSet
。
KVO的基本使用方法
KVO的使用大致分为三步:
一.通过addObserver:forKeyPath:options:context:
方法注册观察者,观察者可以接收keyPath
属性的变化事件。
注册观察者:
1
2
3
4
5
6
7
8/* @observer:就是观察者,是谁想要观测对象的值的改变。 @keyPath:就是想要观察的对象属性。 @options:options一般选择NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld,这样当属性值发生改变时我们可以同时获得旧值和新值,如果我们只填NSKeyValueObservingOptionNew则属性发生改变时只会获得新值。 @context:想要携带的其他信息,比如一个字符串或者字典什么的,不过只能是同一视图的内容。 */ - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@options分为四个类型:
NSKeyValueObservingOptionNew 返回改变后的新值
NSKeyValueObservingOptionOld 返回改变之前的旧值
NSKeyValueObservingOptionInitial 注册的时候就会发一次通知,改变后也会发通知
NSKeyValueObservingOptionPrior 改变之前发一次通知,改变之后发一次通知
在写想要观察的对象属性时,若该对象有套用对象属性的属性可以直接使用点语法监听。
小技巧:
在写想要观察的对象属性时,若该对象没有套用对象则可以使用NSStringFromSelector(@selector(name)) 返回一个字符串,若没有其中的name属性就会报警告,与@"name"有区别,@"name"就算没有该属性也不会报错,编译照常,在后续查找程序问题所在时会比较困难。
二.在观察者中实现observeValueForKeyPath:ofObject:change:context:
方法,当keyPath
属性发生改变后,KVO
会回调这个方法来通知观察者。
监听方法(回调方法):
1
2
3
4
5
6
7
8/* @keyPath:观察的属性 @object:观察的是哪个对象的属性 @change:这是一个字典类型的值,通过键值对显示新的属性值和旧的属性值 @context:上面添加观察者时携带的信息 */ - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
三.当观察者不需要监听时,可以调用removeObserver:forKeyPath:
方法将KVO
移除。
移除监听:
1
2
3
4
5
6/* @observer:就是观察者,是谁想要观测对象的值的改变。 @keyPath:就是想要观察的对象属性。 */ - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
注意:
KVO
只是监听setter
方法,例如像可变数组添加元素的方法(addObject
)它不属于setter
方法,所以即使你向数组中add
多少个元素也不会有监听反应。- 一定要在观察者消失之前调用
removeObserver
,否则会导致Crash
。
KVO的一种缺陷(其实不能称为缺陷,应该称为特性)是,当对同一个keypath进行两次removeObserver时会导致程序crash,这种情况常常出现在父类有一个kvo,父类在dealloc中remove了一次,子类又remove了一次的情况下。不要以为这种情况很少出现!当你封装framework开源给别人用或者多人协作开发时是有可能出现的,而且这种crash很难发现。不知道你发现没,目前的代码中context字段都是nil,那能否利用该字段来标识出到底kvo是superClass注册的,还是self注册的?
回答是可以的。我们可以分别在父类以及本类中定义各自的context字符串,比如在本类中定义context为@“ThisIsMyKVOContextNotSuper”;然后在dealloc中remove observer时指定移除的自身添加的observer。这样iOS就能知道移除的是自己的kvo,而不是父类中的kvo,避免二次remove造成crash。
单界面小实例:
- 先新建一个NSObject的子类Dog
- Dog.h
1
2
3
4
5
6@interface Dog : NSObject @property (nonatomic, assign) int age; @end
- ViewController.h
1
2
3
4
5
6
7
8
9
10
11#import <UIKit/UIKit.h> #import "Dog.h" @interface ViewController : UIViewController @property (nonatomic, strong) UILabel *label; @property (nonatomic, strong) UIButton *button; @property (nonatomic, strong) Dog *dog; @end
- ViewController.m
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
40
41
42
43
44
45@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //初始化UILabel _label = [[UILabel alloc] init]; _label.frame = CGRectMake(100, 300, 200, 50); [self.view addSubview:_label]; //初始化UIButton _button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [_button setTitle:@"add" forState:UIControlStateNormal]; [_button addTarget:self action:@selector(add:) forControlEvents:UIControlEventTouchUpInside]; _button.frame = CGRectMake(150, 400, 60, 40); [self.view addSubview:_button]; //初始化Dog _dog = [[Dog alloc] init]; //注册_dog的监听者 [_dog addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; } //实现UIButton的事件,用来触发监听 - (void)add:(UIButton*)button { static int a = 0; //改变_dog中的age的值,会调用监听方法 _dog.age = a++; } //监听方法,当KVO事件到来时会调用这个方法,如果没有实现会导致Crash - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { //通过调用监听事件,来改变_label显示的内容 NSString *string = [[NSString alloc] initWithFormat:@"狗的年龄是:%d", _dog.age]; _label.text = string; //在控制器中输出改变前的旧值和改变后的新值 NSLog(@"old age: %@ --- new age: %@", [change valueForKey:@"old"], [change valueForKey:@"new"]); } //在不使用的时候移除监听 - (void)dealloc { [_dog removeObserver:self forKeyPath:@"age"]; } @end
- 效果图:
- 点击add按钮
- 控制台输出:
多界面小实例
- 新建的CoolViewController.h
1
2
3
4
5
6
7
8
9#import <UIKit/UIKit.h> @interface CoolViewController : UIViewController<UITextFieldDelegate> @property (nonatomic, copy) NSString *content; @property (nonatomic, strong) UIButton *back; @property (nonatomic, strong) UITextField *textField; @end
- CoolViewController.m
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#import "CoolViewController.h" @interface CoolViewController () @end @implementation CoolViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor orangeColor]; _back = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [_back setTitle:@"back" forState:UIControlStateNormal]; _back.frame = CGRectMake(100, 300, 60, 40); [_back addTarget:self action:@selector(pressBack:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_back]; _textField = [[UITextField alloc] initWithFrame:CGRectMake(100, 400, 250, 50)]; _textField.keyboardType = UIKeyboardTypeDefault; _textField.borderStyle = UITextBorderStyleRoundedRect; _textField.delegate = self; [self.view addSubview:_textField]; } //按钮事件,因为KVO是监听setter事件所以这里的content必须要用self.content点语法来访问,否则无法传回数据 - (void)pressBack:(UIButton*)button { self.content = _textField.text; [self dismissViewControllerAnimated:YES completion:nil]; } @end
- ViewController.h
1
2
3
4
5
6
7
8
9
10
11
12#import <UIKit/UIKit.h> #import "CoolViewController.h" @interface ViewController : UIViewController @property (nonatomic, strong) UILabel *label; @property (nonatomic, strong) UIButton *button; //定义一个视图的属性,便于等等监听 @property (nonatomic, strong) CoolViewController *cool; @end
- ViewController.m
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54#import "ViewController.h" /* NSKeyValueObservingOptionNew 返回改变后的新值 NSKeyValueObservingOptionOld 返回改变之前的旧值 NSKeyValueObservingOptionInitial 注册的时候就会发一次通知,改变后也会发通知 NSKeyValueObservingOptionPrior 改变之前发一次,改变之后发一次 */ @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _label = [[UILabel alloc] init]; _label.frame = CGRectMake(100, 300, 200, 50); _label.text = @"A界面要传递的值"; [self.view addSubview:_label]; _button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [_button setTitle:@"点击跳转" forState:UIControlStateNormal]; [_button addTarget:self action:@selector(add:) forControlEvents:UIControlEventTouchUpInside]; _button.frame = CGRectMake(150, 400, 100, 40); [self.view addSubview:_button]; } - (void)add:(UIButton*)button { //初始化视图对象 _cool = [[CoolViewController alloc] init]; _cool.modalPresentationStyle = UIModalPresentationFullScreen; //为视图中的属性注册一个监听事件 [_cool addObserver:self forKeyPath:@"content" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [self presentViewController:_cool animated:YES completion:nil]; } //监听方法 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { //对监听的属性进行判断,如果是该属性则执行相应的方法 if ([keyPath isEqualToString:@"content"]) { id value = [change valueForKey:@"new"]; _label.text = value; } } //在不使用的时候移除监听 - (void)dealloc { [_cool removeObserver:self forKeyPath:@"content"]; } @end
- 效果图
- 跳转,并在新界面的textField输入
- back回来
KVO的使用场景
KVO用于监听对象属性的改变。
- 下拉刷新、下拉加载监听UIScrollView的contentoffsize;
- tableview混排监听contentsize;
- 监听模型属性实时更新UI;
- 监听控制器frame改变,实现抽屉效果。
最后
以上就是酷酷大炮最近收集整理的关于【iOS】—— KVO传值什么是KVO?KVO的基本使用方法KVO的使用场景的全部内容,更多相关【iOS】——内容请搜索靠谱客的其他文章。
发表评论 取消回复