iOS底层原理之`OC语法`(KVC和KVO)
2018-08-28 14:43
831 查看
版权声明:不积跬步无以至千里,不积小流无以成江海! https://blog.csdn.net/Bolted_snail/article/details/82147675
文章目录
1. KVC
- KVC的全称是
Key-Value Coding
,俗称“键值编码”,可以通过一个key来访问某个属性。 - 常见的API有:
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;//通过keyPath可以设置属性的属性 - (void)setValue:(id)value forKey:(NSString *)key;//通过key设置自己的属性 - (id)valueForKeyPath:(NSString *)keyPath;//通过keyPath访问属性的属性 - (id)valueForKey:(NSString *)key; //通过key访问自己的属性
-
kvc设置属性原理:
-
setValue:forKey
的原理详解:-
先去寻找
setKey:
方法(即对应key的set方法),找到了就去传递参数调用方法; - 如果没找到,再去寻找
_setKey:
的方法,找到可传值; - 没找到就去查看
+ (BOOL)accessInstanceVariablesDirectly;
这个方法是不是设置为YES(默认是YES),如果设置的是YES这表示直可以接访问实例成员变量; - 然后会去查看有没有
_key
的成员变量、有就设值; - 没有就去查看有没有
_isKey
的成员变量、有就设值; - 没有就去查看有没有
key
的成员变量、有就设值; - 没有就去查看有没有
isKey
的成员变量、有就设值; - 如果还没找到就会抛异常:
NSUnknownKeyException
- 如果上面
accessInstanceVariablesDirectly
设置为NO,并且setKey:、_setKey:
这两个set方法没找到就会抛异常,不会去访问属性。
示例1:
@interface Person : NSObject{ @public // int _age; int _isAge; int age; int isAge; } /**age属性*/ //@property(nonatomic,assign) int age; @end @implementation Person //-(void)setAge:(int)age{ // _age = age; //} //-(void)_setAge:(int)age{ // _age = age; //} @end int main(int argc, const char * argv[]) { @autoreleasepool { Person * p = [[Person alloc]init]; [p setValue:@18 forKey:@"age"]; // NSLog(@"%d",p->age); } return 0; }
上面代码和调试结果可以看出:当把两个set方法和
_age成员变量注销后会优先访问
_isAge成员变量,可以通过注销其他的成员变量一次证明方法的顺序,这里不再赘述。
在Person类中重写
+ (BOOL)accessInstanceVariablesDirectly;方法,并设置返回值为
NO,编译就会抛异常。
@implementation Person + (BOOL)accessInstanceVariablesDirectly{ return NO; } @end
- kvc取值原理:
- valueForKey:原理详解:
- 同设置属性一样,取值时访问方法的顺序是
getKey
、key
、isKey
、_key
,找到前面的方法就调用给成员变量取值就不会再往下找了。 - 如果上面的方法都没找到就会去查看accessInstanceVariablesDirectly`这个方法是不是设置为YES,如果设置的是YES这表示直可以接访问实例成员变量;
- 设置成员变量的顺序为:
_key
、_isKey
、key
、iskey
找到前面的成员变量取值就不会再往下找了; - 如果没有找到上面的成员变量,就会抛异常:
NSUnknownKeyException
- 如果上面
accessInstanceVariablesDirectly
设置为NO,并且上面的方法都没有找到也会抛异常。
注意
:上面的赋值取值方法以及成员变量可以是私有的,依然可以访问,即KVC是可以访问对象的私有属性的。
//.m文件 #import "Person.h" @interface Person(){ @private int _age; int _isAge; int age; int isAge; } @end int main(int argc, const char * argv[]) { @autoreleasepool { Person * p = [[Person alloc]init]; [p setValue:@18 forKey:@"age"]; NSLog(@"%@",[p valueForKey:@"age"]); } return 0; }
2. KVO
KVO
的全称是Key-Value Observing
,俗称“键值监听”,可以用于监听某个对象属性值的改变。- 使用KVO分为三个步骤:
1. 通过- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
方法注册观察者,观察者可以接收keyPath
属性的变化事件。
2. 在观察者中实现-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
方法,当keyPath
属性发生改变后,KVO会回调这个方法来通知观察者。
3. 当观察者不需要监听时,可以调用- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
方法将KVO移除。需要注意的是,调用removeObserver
需要在观察者消失之前,否则会导致Crash
4. 提示:通过kvc
修改属性也是可以触发监听的。 - 示例
#import <Foundation/Foundation.h> @interface Person : NSObject /**name*/ @property(nonatomic,copy) NSString * name ; /**age*/ @property(nonatomic,assign) int age; @end #import "ViewController.h" #import "Person.h" @interface ViewController () /**person */ @property(nonatomic,strong) Person * person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.person = [[Person alloc]init]; self.person.name = @"a"; self.person.age = 16; //给person添加kvo监听,监听age和name属性值的变化 /* observer:监听者 keyPath:属性对应的键 options:监听内容,一般是监听新值和旧值 context:可以传递一些参数 */ [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"参数1"]; [self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"参数2"]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.person.name = @"aa"; self.person.age = 18; } // 当监听对象的属性值发生改变时,就会调用 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{ NSLog(@"监听到%@的%@属性值改变了 - % 20000 @ - %@", object, keyPath, change, context); } //移除对应的监听者 -(void)dealloc{ [self.person removeObserver:self forKeyPath:@"name"]; [self.person removeObserver:self forKeyPath:@"age"]; } @end
- 打印结果:
- 底层原理
未使用KVO监听对象时,直接调用set方法赋值:
使用的KVO监听对象时,内部方法调用顺序示例:
#import <Foundation/Foundation.h> @interface Person : NSObject /**age*/ @property(nonatomic,assign) int age; @end @implementation Person -(void)setAge:(int)age{ _age = age; NSLog(@"setAge:"); } -(void)willChangeValueForKey:(NSString *)key{ [super willChangeValueForKey:key]; NSLog(@"%@:willChangeValueForKey",key); } - (void)didChangeValueForKey:(NSString *)key{ NSLog(@"%@:didChangeValueForKey - begin",key); [super didChangeValueForKey:key]; NSLog(@"%@:didChangeValueForKey - end",key); } @end #import "ViewController.h" #import "Person.h" @interface ViewController () /**person */ @property(nonatomic,strong) Person * person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.person = [[Person alloc]init]; self.person.age = 16; [self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"参数2"]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.person.age = 18; } // 当监听对象的属性值发生改变时,就会调用 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{ NSLog(@"监听到%@的%@属性值改变了- %@", object, keyPath, context); } -(void)dealloc{ [self.person removeObserver:self forKeyPath:@"age"]; } @end
打印结果:
通过转换为的cpp代码时,我们可以发现使用的KVO监听对象时:
- 当修改对象属性时会通过
RuntimeAPI
动态生成一个子类NSKVONotifying_xxx
,让实例对象的isa
指向这个全新的子类,新生成的子类重写了setter/getter
方法,其中setter
方法中会调用Foundation
的_NSSetXXXValueAndNotify
函数; _NSSetXXXValueAndNotify
内部又调用了willChangeValueForKey:
、父类原来的setter
、didChangeValueForKey:
;- 当调用
didChangeValueForKey:
方法时就会触发监听器调用-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
方法。
- 新生成的子类内部实现如下:
#import "NSKVONotifying_MJPerson.h" @implementation NSKVONotifying_MJPerson - (void)setAge:(int)age { _NSSetIntValueAndNotify(); } // 伪代码 void _NSSetIntValueAndNotify() { [self willChangeValueForKey:@"age"]; [super setAge:age]; [self didChangeValueForKey:@"age"]; } - (void)didChangeValueForKey:(NSString *)key { // 通知监听器,某某属性值发生了改变 [oberser observeValueForKeyPath:key ofObject:self change:nil context:nil]; } // 屏幕内部实现,隐藏了NSKVONotifying_MJPerson类的存在 - (Class)class { return [MJPerson class]; } - (void)dealloc { // 收尾工作 } - (BOOL)_isKVOA { return YES; } @end
- 面试题:
- iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
利用RuntimeAPI
动态生成一个子类,并且让instance
对象的isa
指向这个全新的子类;
当修改instance
对象的属性时,会调用Foundatio
n的_NSSetXXXValueAndNotify
函数、
willChangeValueForKey:
、
父类原来的setter
、
didChangeValueForKey:
、
内部会触发监听器(Oberser
)的监听方法(observeValueForKeyPath:ofObject:change:context:
) - 直接修改成员变量会触发KVO么?
不会触发KVO。 - 如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:
,或者通过kvc
去修改属性。
手动触发KVO示例:
#import <Foundation/Foundation.h> //Person只有一个公开的成员变量 @interface Person : NSObject{ @public int age; } @end //PersonObserver是自定义的监听者 #import "PersonObserver.h" @implementation PersonObserver -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%@的%@值发送变化了!!!",object,keyPath); } @end #import "ViewController.h" #import "Person.h" #import "PersonObserver.h" @interface ViewController () /**person */ @property(nonatomic,strong) Person * person; @property(nonatomic ,strong) PersonObserver * observer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.person = [[Person alloc]init]; self.person -> age = 16; self.observer = [[PersonObserver alloc]init]; [self.person addObserver:self.observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self.person willChangeValueForKey:@"age"]; self.person -> age = 18; // [self.person setValue:@18 forKey:@"age"]; [self.person didChangeValueForKey:@"age"]; } -(void)dealloc{ [self.person removeObserver:self.observer forKeyPath:@"age"]; } @end
结果可见:如果直接通过指针给属性赋值是无法触发监听方法的,但是要是手动调用
Person的
willChangeValueForKey:和
didChangeValueForKey:方法就会触发监听方法,或者通过
kvc也可以直接触发监听方法。
KVO原理分析及使用进阶
相关文章推荐
- iOS KVO底层实现原理
- iOS中KVO的底层实现原理
- KVC和KVO底层原理
- iOS的KVO底层实现原理
- KVC/KVO底层实现原理
- KVC和KVO底层原理
- KVC/KVO 进阶(一) 底层原理
- iOS开发之KVC和KVO原理解析和实例分析
- iOS OC KVC KVO 实现原理详解
- iOS开发之旅--KVO监听对象底层原理
- ios-kvc\kvo 原理
- iOS底层学习-KVC使用实践以及实现原理
- iOS kvo 底层实现原理
- KVC和KVO的理解(底层实现原理)
- IOS 中 KVO,KVC 的区别与联系 KVO 底层实现机制
- ios-kvc\kvo 原理
- ios中对KVO、KVC的一些理解
- iOS OC语言: Block底层实现原理 (转载)
- iOS之KVC和KVO
- iOS KVC KVO(键值编码,键值观察者)