开发技巧|iOS面试题库——KVC与KVO
KVC与KVO
- KVC与KVO
- 1.1KVC
- 1.1.1 valueForKey:
- 1.1.2 setValue:forKey:
- 1.2 KVO
- 1.2.1 使用
- 1.2.2 原理
- 1.1KVC
本文将会详解在面试中的常客——KVO实现的原理,在了解KVO之前我们要对KVC进行一个全面的了解,毕竟连官方文档都提到过:
important: In order to understand key-value observing, you must first understand key-value coding.1.1KVC KVC全称:Key-value coding(键-值编码),通过KVC机制我们可以间接的访问对象的属性。而KVC之所以能够访问属性是因为对象遵守了一个非正式的
NSKeyValueCoding
协议(NSObject开始就遵守了此协议,所以继承自NSObject的对象都可以使用KVC)。开发中我们都知道在对于属性可以使用getter
和setter
或者直接使用实例变量来进行直接访问和修改。但这些访问方式是需要依靠属性的get
方法、set
方法、变量名。随着对象定义的属性增加或者变动。编译器生成的这些getter
、setter
会越来越多。KVC则是通过是用字符串的名字Key来对属性进行访问和修改。KVC中最关键的两个方法:
-valueForKey:
-setValue:forKey:
1.1.1 valueForKey:
-valueForKey:
是通过\来获取属性的值。在一个对象实例中按get
、
、is
、_
顺序匹配。命中的Value的类型如果是对象直接返回。如果命中的Value是能被包装成NSNumber的数值类型。包装成NSNumber返回。不支持NSNumber的数值类型则包装成NSValue返回。如果没用命中调用-valueForUndefinedKey:
抛出异常,valueForUndefinedKey:
可在子类中重写忽略抛出的异常,自己处理。@interface Person
@property (nonatomic, assign) CGFloat height;
@end
p.height = 119.0;
NSNumber *height = [p valueForKey:@"height"];
//CGFloat 包装成NSNumber。
注意1.1.2 setValue:forKey:
- 上述的查找过程中省略了很多其他情况下的查找
countOf
,objectIn
,AtIndex: countOf
,enumeratorOf
,memberOf
,有兴趣的同学可以去Search Pattern for the Basic Getter查看详细的查询路径。: - 非Object对象包装成NSNumber或NSValue列表查看Representing Non-Object Values
-setValue:forKey:
同-valueForKey:
也是根据给定的\匹配方法名set:
、_set
,如果命中,调用方法将Value作为参数传入。未命中则看+ accessInstanceVariablesDirectly
是否返回YES,YES则按 _
, _is
,
, is
顺序匹配实例变量。如果命中直接将Value给变量赋值。NO则调用-setValue:forUndefinedKey:
抛出异常。-setValue:forUndefinedKey:
也可以被子类重写。// setValue: forKey:使用
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
1.2 KVO 1.2.1 使用
KVO
在iOS中是观察者模式的一种表现。我们可以使用KVO
让某个对象成为另外一个对象的监听者。当被监听对象的属性发生改变时,KVO
就会通知监听者。关于
KVO
的使用网上有很多教程,KVO使用主要是三个步骤:- 调用
addObserver:forKeyPath:options:context:
注册成为监听者。 - 监听者实现
observeValueForKey:ofObject:change:context:
方法。 - 调用
removeObserver:forKeyPath:
移除监听
KVO
的实现是建立在KVC
的基础上的。即被监听的属性必须能满足KVC
的,才能是用KVO
来监听。KVO的自动触发监听通知的方法系列:
// Call the accessor method.
[account setName:@"Savings"];
// Use setValue:forKey:.
[account setValue:@"Savings" forKey:@"name"];
// Use a key path, where 'account' is a kvc-compliant property of 'document'.
[document setValue:@"Savings" forKeyPath:@"account.name"];
// Use mutableArrayValueForKey: to retrieve a relationship proxy object.
Transaction *newTransaction = <#Create a new transaction for the account#>;
NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
[transactions addObject:newTransaction];
KVO的手动触发监听通知:
// 关闭balance的自动触发
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
要实现手动触发监听,你要执行在变更前执行
willChangeValueForKey:
方法,在变更后执行didChangeValueForKey:
方法:- (void)setBalance:(double)balance {
[self willChangeValueForKey:@"balance"];
_balance = balance;
[self didChangeValueForKey:@"balance"];
}
1.2.2 原理
>
Automatic key-value observing is implemented using a technique called isa-swizzling.
上面这句话在Key-Value Observing Implementation Details提到。意思就是KVO的是通过一种叫
isa-swizzling
的技术实现的。查看NSObject.h文件。
@interface NSObject {
Class isa;
}
control + command
点击Class
,看到Class
实际上是一个指向obj_class结构体的指针。typedef struct objc_class *Class;
struct objc_class {
struct objc_class * isa;
// 原始的代码 Class isa;
#if !__OBJC2__
Class _Nullable super_classOBJC2_UNAVAILABLE;
const char * _Nonnull nameOBJC2_UNAVAILABLE;
long versionOBJC2_UNAVAILABLE;
long infoOBJC2_UNAVAILABLE;
long instance_sizeOBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivarsOBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodListsOBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cacheOBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocolsOBJC2_UNAVAILABLE;
#endif} OBJC2_UNAVAILABLE;
可以看到在
objc_class
结构体中还有一个isa,即类中还有一个指向objc_class
的指针。类的isa指向的是元类(metaClass
)的。如果将对象看成是通过类的实例。那么类就是元类的实例咯?
。这篇文章中我们仅了解什么是
Class isa
。objc_class
结构体中的其它部分会单开一篇runtime
的文章来写。typedef struct objc_object *id;
struct objc_object {
struct objc_class * isa;
// 原始的代码 Class isa;
};
id
在obj
中可以代表个对象。id又是一个objc_object
结构体的指针,且objc_object
结构体中的isa
指向的是该对象的类。在这里是时候祭出火遍
runtime
界的图: 文章图片
.
回到
KVO
。前文提到的isa-swizzling
对于了解过runtime
的同学可能对此有点眼熟。method swizzling也是runtime
的一种黑魔法。可以通过method swizzling
进行方法的互换。回到KVO
上面,我们猜测isa-swizzling
就是类似method swizzling
,只不过是对Class的交换。在KVO中当一个监听者被注册被监听的对象上时。被监听对象的
isa
指针已经被更改了。被监听对象的isa
指针被修改为指向为一个中间类。改中间类可能是该类的子类。重写了被监听对象的属性。然后在改属性值被修改时,会触发监听通知。【开发技巧|iOS面试题库——KVC与KVO】
KVO
的详细流程- 监听者调用监听的方法。
- 被监听者派生一个中间类。被监听对象的
isa
指针指向派生类 - 被监听的属性发生变化,由中间类触发监听通知(具体方式未知)。
- 监听者收到通知。触发
observeValueForKey:ofObject:change:context:
推荐阅读
- 深入理解Go之generate
- 【译】20个更有效地使用谷歌搜索的技巧
- 2020-04-07vue中Axios的封装和API接口的管理
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- iOS中的Block
- 记录iOS生成分享图片的一些问题,根据UIView生成固定尺寸的分享图片
- 数据库设计与优化
- 2019-08-29|2019-08-29 iOS13适配那点事
- Hacking|Hacking with iOS: SwiftUI Edition - SnowSeeker 项目(一)
- iOS面试题--基础