博客> 自定义的 KVO 的用法
自定义的 KVO 的用法
2018-08-12 15:00 评论:0 阅读:100 iOS雯Ping
kvo 自定义的

1.KVO (Key-Value Observing)是什么?

KVO 是 Objective-C 对观察者模式(Observer Pattern)的实现。也是 Cocoa Binding 的基础。当被观察对象的某个属性发生更改时,观察者对象会获得通知。

2.KVO 实现原理

1.当使用观察者模式观察一个对象时,KVO机制会在运行期动态创建一个对象当前类的子类,如果当前类为Yinker,动态创建的子类就是NSKVONotifying_Yinker

2.这个新的子类重写了被观察属性 keyPath 的 setter 方法,在 setter 方法内调用了 NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变更;当改变发生后,didChangeValueForKey:被调用,通知系统该 keyPath 的属性值已经变更;之后,observeValueForKey:ofObject:change:context:会被调用。

3.被观察对象的isa指针会被修改成指向新创建的子类,被观察对象也就成了新创建的子类的实例

4.Apple 重写了class方法,隐藏新创建的子类,通过class方法获取的还是原来的类。

接下来进行一个简单的验证:

实例.jpeg 在三个断点处获取[yinker class]和object_getClass(yinker)的输出内容:

(lldb) po [yinkerclass]

Yinker

(lldb) po object_getClass(yinker)

Yinker (lldb) po [yinkerclass]

Yinker

(lldb) po object_getClass(yinker)

NSKVONotifying_Yinker (lldb) po [yinkerclass]

Yinker

(lldb) po object_getClass(yinker)

Yinker 从上面可以看出来,在添加观察者之后,对象 isa 指向了NSKVONotifying_Yinker,证明了确实新生成了一个新的子类。但是通过class获取的还是Yinker,这就验证了上面说的 Apple 重写了class方法,隐藏新创建的子类。而在移除观察者之后,又变回了原来的样子。

3.KVO 实现机制

KVO 的实现也依赖于 Objective-C 强大的 Runtime 。Apple 的文档有简单提到过 KVO 的实现:

Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ... 4.KVO用法 1.添加监听

//在Person.h中增加一个属性age

@interfacePerson:NSObjec

@property(nonatomic,assign)NSIntegerage;

@end 2.接收通知

-(void)observeValueForKeyPath:(nullableNSString)keyPathofObject:(nullableid)objectchange:(nullableNSDictionary)changecontext:(nullablevoid*)context 3.移除监听

  • (void)removeObserver:(NSObject)observer forKeyPath:(NSString)keyPath context:(nullablevoid*)context (系统还有其他移除方法)

自定义的 KVO 的用法:

第一步 : 注册KVO

  • (void)secondRegisteCustomKVO

{

//

if (!self.message) {

    self.message = [[SecondMessage alloc] init];

}

NSString *key = NSStringFromSelector(@selector(text));

[self.message PG_addObserber:self forKey:key withBlock:^(id observingObject, NSString *observedKey, id oldValue, id newValue) {

    NSLog(@"%@ . %@ is now:%@",observingObject,observedKey,newValue);

    dispatch_async(dispatch_get_main_queue(), ^{

        self.textField.text = newValue;

    });

}];

[self onCustomKVOButtonClick:nil];

} 第二步 :移除KVO

  • (void)viewWillDisappear:(BOOL)animated

{

// 如果不移除掉的话会造成 内存泄漏

NSString *key = NSStringFromSelector(@selector(text));

NSLog(@"key is %@",key);

[self.message PG_removeObserver:self forKey:key];

} 但是 还是 出现了问题 如果 被观察者的属性 是 基本数据类型 例如 int ,float 等的类型。笔者 发现 原因出在 NSObject+KVO分类中的 自定义的 setter 方法

pragma mark ---- 很明显这个 setter 方法 和getter 方法 只是 写了id 类型 的没写 基础类型的 比如int float 等 笔者要加上这些类型 Thread 1: EXC_BAD_ACCESS (code=1, address=0x2)

static void kvo_setter(id self,SEL _cmd,id newValue)

实例:

1.创建model

ObserveObject.h:@interfaceObserveObject:NSObject

@property(nonatomic,strong)NSString*name;

@property(nonatomic)NSIntegerage;

@end 2 .创建被观察者

ObserveObject *observe = [[ObserveObject alloc] init];

observe.name = @"tom";

observe.age = 20; 3 .添加观察者

[observe addObserver:selfforKeyPath:@"name"options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNewcontext:nil]; 4 .实现监听方法

  • (void)observeValueForKeyPath:(NSString)keyPath ofObject:(id)object change:(NSDictionary )change context:(void*)context {

if([keyPath isEqualToString:@"name"]) {

NSString*oldName = change[NSKeyValueChangeOldKey];

NSString*newName = change[NSKeyValueChangeNewKey];

NSLog(@"old name = %@, new name = %@",oldName,newName); }} 5 .移除监听

-(void)removeObserver:(NSObject)observerforKeyPath:(NSString)keyPathcontext:(nullablevoid*)contextAPI_AVAILABLE(macos(10.7),ios(5.0),watchos(2.0),tvos(9.0)); 一般在dealloc方法中调用。这里有一个问题需要了解下,注册和移除是一一对应的,有几个注册就要有几个移除。如果一个类中有多个监听对象,可以有context值来区分。

( 其实作为一个开发者有一个学习的氛围跟一个交流圈子特别重要,这是我的一个iOS交流群687528266,不管是小白还是大牛都欢迎入驻,大家一起交流成长! )

收藏
0
sina weixin mail 回到顶部