- @synthesize:如果不实现setter和getter方法,将按照编译器的规则自动生成setter和getter方法.
- @dynamic:直接或动态的执行setter和getter方法.通常自己实现setter和getter方法,我们基本上不会用到.
@dynamic
这个关键词用的不多,最常见的地方还是在CoreData的NSManagedObject中会看见。
@dynamic A相当于告诉编译器:“参数A的getter和setter方法并不在此处,而在其他地方实现了或者生成了,当你程序运行的时候你就知道了,所以别警告我了”这样程序在运行的时候,对应参数的getter和setter方法就会在其他地方去寻找,比如父类。
何时用到
比如类遵守一个delegate时,delegate中的参数在父类的初始化方法中就赋值了,而子类的没有自己的初始化方法的时候,子类中的此参数就可以设为@dynamic,即和父类共用一个参数
KVO概述
键值观察Key-Value-Observer就是观察者模式。
观察者模式的定义:一个目标对象管理所有依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象。这个主动通知通常是通过调用各观察者对象所提供的接口方法来实现的。观察者模式较完美地将目标对象与观察者对象解耦。
当需要检测其他类的属性值变化,但又不想被观察的类知道,有点像FBI监视嫌疑人,这个时候就可以使用KVO了。
KVO同KVC一样都依赖于Runtime的动态机制
KVO实现步骤
*注册
//keyPath就是要观察的属性值
//options给你观察键值变化的选择
//context方便传输你需要的数据
-(void)addObserver:(NSObject *)anObserver
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context
- 实现方法
//change里存储了一些变化的数据,比如变化前的数据,变化后的数据;如果注册时context不为空,这里context就能接收到。
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
- 移除
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
KVO的实现分析
使用观察者模式需要被观察者的配合,当被观察者的状态发生变化的时候通过事先定义好的接口(协议)通知观察者。在KVO的使用中我们并不需要向被观察者添加额外的代码,就能在被观察的属性变化的时候得到通知,这个功能是如何实现的呢?同KVC一样依赖
于强大的Runtime机制。
KVO的基本原理大概是这样的
当一个对象被观察时, 系统会新建一个子类NSNotifying_A ,在子类中重写了对象被观察属性的 set方法, 并且改变了该对象的 isa 指针的指向(指向了新建的子类) , 当属性的值发生改变了, 会调用子类的set方法, 然后发出通知
二. 当keyPath 为对象时, 改对象有许多属性, 怎么办?
在person类中,重写这个方法, 设置需要观察的属性 , 注意:"_dog.level"
//返回一个容器, 里面放的是NSString类型
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
//观察dog对象中的所有属性
if ([key isEqualToString:@"dog"]) {
keyPaths = [keyPaths setByAddingObjectsFromArray:@[@"_dog.level",@"_dog.age"]];
}
return keyPaths;
}
三. 手动触发KVO
系统默认该对象的所有属性 都能被观察到 ,重写下面方法, 可以单独设置某个属性不能被观察
//默认 yes, 默认自动观察所有属性
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
return YES;
}
//返回NO, 则不能被默认观察到name
+ (BOOL)automaticallyNotifiesObserversOfName{
return NO;
}
+ (BOOL)automaticallyNotifiesObserversOfAge{
return YES;
}
当 name 发生改变的时, 手动触发, 发送通知
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//手动发通知
//即将改变(发一次通知)
[_person willChangeValueForKey:@"name"];
_person.name = @"dddd";
//已经改变(发一次通知),一共发了两次通知
[_person didChangeValueForKey:@"name"];
}
通过runtime的方式, 动态创建一个类, 并给该类添加方法
#import "NSObject+XSKVO.h"
#import <objc/runtime.h>
@implementation NSObject (XSKVO)
- (void)xs_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
//1.新建一个类
NSString *className = [@"XSKVO" stringByAppendingString: NSStringFromClass([self class])];
Class newClass = objc_allocateClassPair([self class], className.UTF8String, 0);
//注册类
objc_registerClassPair(newClass);
//2.修改 调用者类型
object_setClass(self, newClass);
//3.给子类添加set方法(子类里面没有set方法的)
//OC方法:方法编号SEL ,方法实现IMP
class_addMethod(newClass, @selector(setName:), xssetName, "");
}
/*
隐藏的参数:
self 方法的调用者
_cmd 方法的编号
*/
void xssetName(id self,SEL _cmd, NSString *newName){
NSLog(@"自定义的实现%@",newName);
//方案一:通过消息机制 发送消息 -observeValueForKeyPath
}
@end
系统实现KVO有以下几个步骤:
当类A的对象第一次被观察的时候,系统会在运行期动态创建类A的派生类。我们称为B。
在派生类B中重写类A的setter方法,B类在被重写的setter方法中实现通知机制。
类B重写会 class方法,将自己伪装成类A。类B还会重写dealloc方法释放资源。
系统将所有指向类A对象的isa指针指向类B的对象。
KVO同KVC一样,通过 isa-swizzling 技术来实现。当观察者被注册为一个对象的属性的观察对象的isa指针被修改,指向一个中间类,而不是在真实的类。其结果是,isa指针的值并不一定反映实例的实际类。
所以不能依靠isa指针来确定对象是否是一个类的成员。应该使用class方法来确定对象实例的类。
网友评论