目录
- 1. KVO的使用
- 1.1 KVO基本使用方法
- 1.2 KVO手动触发模式
- 1.3 KVO属性依赖
- 1.4 KVO容器类监听
- 2. KVO底层实现
1. KVO的使用
1.1 KVO基本使用方法
- 被监听对象调用
-addObserver:forKeyPath:options:context:
方法,传入监听者、被监听对象的属性(或keyPath) - 监听者实现
-observeValueForKeyPath:ofObject:change:context:
方法,获得监听回调 - 最后记得在
-dealloc
方法里移除监听-removeObserver:forKeyPath:
实例:假设有类Person,你需要监听它的name变化情况
//Person.h
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end
//Person.m
@implementation Person
@end
//ViewController.h
@interface ViewController : UIViewController
@property (strong, nonatomic) Person *person;
@end
//ViewController.m
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
//添加监听代码
self.person = [Person new];
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
}
//获得回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"%@", change);
}
- (void)dealloc {
//移除监听
[self.person removeObserver:self forKeyPath:@"name"];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.person.name = @"herui";
}
@end
1.2 KVO手动触发模式
- 被监听类实现
+automaticallyNotifiesObserversForKey:
方法,返回NO,标识为手动触发模式 - 当被监听对象属性的set方法被调用时不再触发监听回调,而是需要调用
-willChangeValueForKey:
以及-didChangeValueForKey:
方法方能触发监听回调
//Person.h
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end
//Person.m
@implementation Person
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return NO;
}
@end
//ViewController.h
@interface ViewController : UIViewController
@property (strong, nonatomic) Person *person;
@end
//ViewController.m
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
//添加监听代码
self.person = [Person new];
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
}
//实现监听回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"%@", change);
}
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"name"];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.person willChangeValueForKey:@"name"];
// self.person.name = @"herui";
[self.person didChangeValueForKey:@"name"];
}
@end
1.3 KVO属性依赖
- 被监听类实现
+keyPathsForValuesAffectingValueForKey:
方法并返回需要被依赖属性的set集合
实例:假设Person类有一个属性Dog,而Dog又有属性nick、level,你需要监听这两个属性的变化情况。有两种方式可以做到:
- 多写几次
addObserver
、removeObserver
- KVO属性依赖;
假设Dog有很多个需要被监听的属性呢?你准备写多少次?
//Dog.h
@interface Dog : NSObject
@property (copy, nonatomic) NSString *nick;
@property (assign, nonatomic) int level;
@end
//Dog.m
@implementation Dog
@end
//Person.h
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@property (strong, nonatomic) Dog *dog;
@end
//Person.m
@implementation Person
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
if([key isEqualToString:@"dog"]){
return [[NSSet alloc] initWithObjects:@"_dog.nick", @"_dog.level", nil];
}
return [super keyPathsForValuesAffectingValueForKey:key];
}
@end
//ViewController.h
@interface ViewController : UIViewController
@property (strong, nonatomic) Person *person;
@end
//ViewController.m
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
//添加监听代码
self.person = [Person new];
self.person.dog = [Dog new];
[self.person addObserver:self forKeyPath:@"dog" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
}
//实现监听回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"%@", change);
}
- (void)dealloc {
//移除监听
[self.person removeObserver:self forKeyPath:@"dog"];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.person.dog.nick = @"小白";
self.person.dog.level = 1;
}
@end
1.4 KVO容器类监听
- 不能直接拿被监听容器对象进行操作,而是要通过
-mutableArrayValueForKey:
-mutableSetValueForKey:
方法来获取到被监听的容器对象,再对它进行操作才能触发监听回调
可以监听到数组、集合中的元素变化情况
@interface MyViewController ()
@property (strong, nonatomic) NSMutableArray *array;
@end
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.array = [NSMutableArray array];
[self addObserver:self forKeyPath:@"array" options:(NSKeyValueObservingOptionNew) context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"%@ - %@", keyPath, change);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//重点在这里
NSMutableArray *arrM = [self mutableArrayValueForKey:@"array"];
[arrM addObject:@1];
}
@end
2. KVO底层实现
- 创建子类(代码无污染)
- 重写setter方法
- 修改isa指针
//NSObject+KVO.h
@interface NSObject (KVO)
- (void)addObserver:(NSObject *)observer forKey:(NSString *)key;
@end
//NSObject+KVO.m
#import
#import
@implementation NSObject (KVO)
- (SEL)setterSELForKey:(NSString *)key {
return NSSelectorFromString([NSString stringWithFormat:@"set%@:", [key capitalizedString]]);
}
- (void)addObserver:(NSObject *)observer forKey:(NSString *)key {
//创建并注册子类
NSString *className = [@"KVO_" stringByAppendingString:NSStringFromClass(self.class)];
Class cls = objc_allocateClassPair(self.class, className.UTF8String, 0);
objc_registerClassPair(cls);
//重写setter方法(添加)
//name: 方法名 SEL
//imp: 方法实现 IMP(即函数地址)
//types: 参数 返回值 的类型编码
class_addMethod(cls, [self setterSELForKey:key], (IMP)setter, "v@:@");
//修改isa
object_setClass(self, cls);
//保存参数
objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_ASSIGN);
objc_setAssociatedObject(self, "key", key, OBJC_ASSOCIATION_ASSIGN);
}
void setter(id self, SEL _cmd, id value)
{
id key = objc_getAssociatedObject(self, "key");
id observer = objc_getAssociatedObject(self, "observer");
//调用父类方法(改isa指针后,直接调用)
Class cls = [self class];
object_setClass(self, class_getSuperclass(cls));
objc_msgSend(self, [self setterSELForKey:key], value);
//通知监听者
if([observer respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]){
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), key, self, @{@"new":value}, nil);
}
//恢复isa指针
object_setClass(self, cls);
}
@end