[TOC]
KVO
研究
没有使用KVO和使用KVO的变化
测试的类Person
@interface Person : NSObject
@property (nonatomic, assign) int age;
@end
@implementation Person
@end
通过 objc_copyClassList
验证
思路: 使用runmtime
的 objc_copyClassList
获取所有的类,对比前后的变化
未使用KVO
unsigned int count = 0;
Class *classes = objc_copyClassList(&count);
for (int i = 0; i < count; i++) {
Class class = classes[i];
const char *name = class_getName(class);
NSLog(@"classname: %s", name);
}
输出:
classname: Person
实际上会打印很多类,上面只截取了相关的类
使用了KVO (注意:这里需要先添加监听,然后再使用objc_copyClassList
方法获取,否则是获取不到的,因为KVO
是动态添加的)
Person *obj = [[Person alloc] init];
[obj addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
unsigned int count = 0;
Class *classes = objc_copyClassList(&count);
for (int i = 0; i < count; i++) {
Class class = classes[i];
const char *name = class_getName(class);
NSLog(@"classname: %s", name);
}
输出:
classname: Person
classname: NSKVONotifying_Person
可以看到多了一个类 NSKVONotifying_Person
通过 isa
来验证
- 添加
kvo
前后isa
指向:- 之前:
Person
- 之后:
NSKVONotifying_Person
- 之前:
- 添加
kvo
前后po
命令输出的都是Person
小结:
1、使用KVO
,runtime
会给需要监听的类Person
自动创建一个类NSKVONotifying_Person
2、并且把原来类创建的对象的isa
指向了新建的类NSKVONotifying_Person
通过 objc
源码可以看到 Class changeIsa(Class newCls);
这个函数来改变 isa
自动创建的类 NSKVONotifying_Person
的一些特性
使用objc_getClass
直接获取类
父类/元类
测试代码如下:
self.person = [[Person alloc] init];
NSLog(@"添加KVO之前 ======");
{
Class objClass = object_getClass(self.person);
Class metaClass = objc_getMetaClass(class_getName(objClass));
NSLog(@"objClass: %@", objClass);
NSLog(@"metaclass: %@", metaClass);
Class superClass = class_getSuperclass(objClass);
Class superMetaClass = class_getSuperclass(metaClass);
NSLog(@"superClass: %@", superClass);
NSLog(@"superMetaClass: %@", superMetaClass);
}
NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
[self.person addObserver:self
forKeyPath:@"age"
options:options
context:nil];
NSLog(@"添加KVO之后 ======");
{
Class objClass = object_getClass(self.person);
Class metaClass = objc_getMetaClass(class_getName(objClass));
NSLog(@"objClass: %@", objClass);
NSLog(@"metaclass: %@", metaClass);
Class superClass = class_getSuperclass(objClass);
Class superMetaClass = class_getSuperclass(metaClass);
NSLog(@"superClass: %@", superClass);
NSLog(@"superMetaClass: %@", superMetaClass);
}
输出结果如下:
添加KVO之前 ======
objClass: Person
metaclass: Person
superClass: NSObject
superMetaClass: NSObject
添加KVO之后 ======
objClass: NSKVONotifying_Person
metaclass: NSKVONotifying_Person
superClass: Person
superMetaClass: Person
小结:
因此KVO就是:
1、通过运行时runtime
新建了一个继承于原来类(Person)的子类(NSKVONotifying_Person)
2、并且把原来类创建的对象的isa
指向了新建的类NSKVONotifying_Person
成员变量
Class kvoClass = objc_getClass("NSKVONotifying_Person");
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(kvoClass, &count);
NSLog(@"count: %@", @(count));
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
ptrdiff_t offset = ivar_getOffset(ivar);
const char *type = ivar_getTypeEncoding(ivar);
NSLog(@"======");
NSLog(@"name: %s", name);
NSLog(@"offset: %td", offset);
NSLog(@"type: %s", type);
}
输出如下:
count: 0
小结:
KVO
生成的子类NSKVONotifying_Person
没有额外的成员变量
属性
Class kvoClass = objc_getClass("NSKVONotifying_Person");
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(kvoClass, &propertyCount);
NSLog(@"propertyCount: %d", propertyCount);
for (int j = 0; j < propertyCount ; j++) {
objc_property_t property = properties[j];
const char *name = property_getName(property);
NSLog(@"propertyName: %s", name);
const char *attributes = property_getAttributes(property);
NSLog(@"propertyAttributes: %s", attributes);
}
输出:
propertyCount: 0
小结:
KVO
生成的子类NSKVONotifying_Person
,没有添加额外的属性
实例方法
Class kvoClass = objc_getClass("NSKVONotifying_Person");
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(kvoClass, &methodCount);
NSLog(@"methodCount: %d", methodCount);
for (int k = 0; k < methodCount; k++) {
Method method = methods[k];
const char *name = sel_getName(method_getName(method));
const char *typeEncoding = method_getTypeEncoding(method);
NSLog(@"分割线 --------------");
NSLog(@"methodName: %s", name);
NSLog(@"methodTypeEncoding: %s", typeEncoding);
}
输出:
methodCount: 4
分割线 --------------
methodName: setAge:
methodTypeEncoding: v20@0:8i16
分割线 --------------
methodName: class
methodTypeEncoding: #16@0:8
分割线 --------------
methodName: dealloc
methodTypeEncoding: v16@0:8
分割线 --------------
methodName: _isKVOA
methodTypeEncoding: B16@0:8
小结:
1、KVO
生成的子类NSKVONotifying_Person
多了四个实例方法
setAge:
: 参数为int
,返回值为void
,父类Person
也有这个方法,子类重写class
: 没有参数,返回值为#
,表示class
,也就是重写了class
方法_isKVOA
: 没有参数,返回值类型为BOOL
dealloc
: 重写了dealloc
方法
class
和 _isKVOA
方法
Class kvoClass = objc_getClass("NSKVONotifying_Person");
id kvoobj = [[kvoClass alloc] init];
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(kvoClass, &methodCount);
NSLog(@"methodCount: %d", methodCount);
for (int k = 0; k < methodCount; k++) {
Method method = methods[k];
const char *name = sel_getName(method_getName(method));
const char *typeEncoding = method_getTypeEncoding(method);
NSLog(@"分割线 --------------");
NSLog(@"methodName: %s", name);
NSLog(@"methodTypeEncoding: %s", typeEncoding);
if ([[NSString stringWithCString:name encoding:NSUTF8StringEncoding] isEqualToString:@"_isKVOA"]) {
bool result = ((bool (*)(id, SEL))(void *)objc_msgSend)((id)kvoobj, method_getName(method));
NSLog(@"_isKVOA: %@", @(result));
}
if ([kvoobj respondsToSelector:@selector(class)]) {
id resut = [kvoobj class];
NSLog(@"class: %@", resut);
}
}
输出结果为:
-
_isKVOA: 1
: 表示返回值是YES
-
class: Person
: 还是指向了原来的类,不需要公开
重写的 setter
方法
self.person = [[Person alloc] init];
NSLog(@"添加KVO之前 ======");
{
IMP imp = [self.person methodForSelector:@selector(setAge:)];
NSLog(@"%p", imp);
}
NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
[self.person addObserver:self
forKeyPath:@"age"
options:options
context:nil];
NSLog(@"添加KVO之后 ======");
{
IMP imp = [self.person methodForSelector:@selector(setAge:)];
NSLog(@"%p", imp);
}
结果如下:
- 使用
KVO
之后setter
方法的实现改变了 (Foundation`_NSSetIntValueAndNotify)
dealloc
方法
最后理一下原理:
小结:
KVO
生成的子类NSKVONotifying_Person
会重写或添加了一些方法
1、是否是KVO监听类的方法_isKVOA
: 返回值是布尔类型B
(表示BOOL
),值为YES
2、重写了需要监听的属性的setter
方法,参数是i
(表示int
) 类型,返回值是v
(表示void
)
3、重写了class
方法,表示所属的类是原来的Person
4、重写了dealloc
方法
类方法
Class kvoClass = objc_getClass("NSKVONotifying_Person");
unsigned int classMethodCount = 0;
// 使用元类
Method *classMethods = class_copyMethodList(objc_getMetaClass(class_getName(kvoClass)), &classMethodCount);
NSLog(@"classMethodCount: %d", classMethodCount);
for (int m = 0; m < classMethodCount; m++) {
Method method = classMethods[m];
const char *name = sel_getName(method_getName(method));
const char *typeEncoding = method_getTypeEncoding(method);
NSLog(@"分割线 --------------");
NSLog(@"methodName: %s", name);
NSLog(@"methodTypeEncoding: %s", typeEncoding);
}
输出:
classMethodCount: 0
小结:
KVO生成的子类NSKVONotifying_Person
没有类方法
断点查看
-
KVO
生成的子类NSKVONotifying_Person
:-
isa
:NSKVONotifying_Person
-
class
方法:NSKVONotifying_Person
-
-
KVO
原来的类Person
-
isa
:NSKVONotifying_Person
,这个是实际的类 -
class
方法:Person
,返回的是原来的类Person
,让我们误以为还是原来的类,其实不是的,通过isa
可以看到
-
属性值变化的监听
NSLog(@"---");
obj.ageForNone = 10;
NSLog(@"1111111");
obj.ageForNone = 20;
NSLog(@"2222222");
obj.ageForNone = 20;
// 监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"keyPath: %@", keyPath);
NSLog(@"object: %@", object);
NSLog(@"change: %@", change);
输出:
keyPath: ageForNone
object: <MyObject: 0x608000012ca0>
change: {
kind = 1;
new = 10;
old = 0;
}
1111111
keyPath: ageForNone
object: <MyObject: 0x608000012ca0>
change: {
kind = 1;
new = 20;
old = 10;
}
2222222
keyPath: ageForNone
object: <MyObject: 0x608000012ca0>
change: {
kind = 1;
new = 20;
old = 20;
}
使用断点进入查看如下:
Foundation`_NSSetIntValueAndNotify:
.......
0x104511ff3 <+101>: movq 0x2ee306(%rip), %rsi ; "willChangeValueForKey:"
......
0x104512010 <+130>: callq 0x1046bcc78 ; symbol stub for: class_getMethodImplementation
......
0x104512020 <+146>: movq 0x2ee2f1(%rip), %rsi ; "didChangeValueForKey:"
......
0x104512072 <+228>: movq 0x2eef17(%rip), %rsi ; "_changeValueForKey:key:key:usingBlock:"
Foundation`NSKeyValueNotifyObserver:
......
0x1044836ec <+41>: movq 0x37d91d(%rip), %rdx ; "_observeValueForKeyPath:ofObject:changeKind:oldValue:newValue:indexes:context:"
0x1044836f3 <+48>: movq 0x37c50e(%rip), %rsi ; "respondsToSelector:"
......
0x104483724 <+97>: movq 0x37d8e5(%rip), %rsi ; "_observeValueForKeyPath:ofObject:changeKind:oldValue:newValue:indexes:context:"
原来的类 Person
的一些特性
成员变量
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self.person class], &count);
NSLog(@"count: %@", @(count));
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
ptrdiff_t offset = ivar_getOffset(ivar);
const char *type = ivar_getTypeEncoding(ivar);
NSLog(@"======");
NSLog(@"name: %s", name);
NSLog(@"offset: %td", offset);
NSLog(@"type: %s", type);
}
输出结果如下:
count: 1
======
name: _age
offset: 8
type: i
小结
1、KVO
生成的子类NSKVONotifying_Person
没有额外的成员变量
2、原来的类Person
有一个成员变量_age
属性
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList([self.person class], &propertyCount);
NSLog(@"propertyCount: %d", propertyCount);
for (int j = 0; j < propertyCount ; j++) {
objc_property_t property = properties[j];
const char *name = property_getName(property);
const char *attributes = property_getAttributes(property);
NSLog(@"======");
NSLog(@"propertyAttributes: %s", attributes);
NSLog(@"propertyName: %s", name);
}
输出结果如下:
propertyCount: 1
======
propertyAttributes: Ti,N,V_age
propertyName: age
小结:
1、KVO
生成的子类NSKVONotifying_Person
,没有添加额外的属性
2、原来的类Person
有一个属性age
实例方法
unsigned int methodCount = 0;
Method *methods = class_copyMethodList([self.person class], &methodCount);
NSLog(@"methodCount: %d", methodCount);
for (int k = 0; k < methodCount; k++) {
Method method = methods[k];
const char *name = sel_getName(method_getName(method));
const char *typeEncoding = method_getTypeEncoding(method);
NSLog(@"分割线 --------------");
NSLog(@"methodName: %s", name);
NSLog(@"methodTypeEncoding: %s", typeEncoding);
}
输出结果如下:
methodCount: 2
分割线 --------------
methodName: setAge:
methodTypeEncoding: v20@0:8i16
分割线 --------------
methodName: age
methodTypeEncoding: i16@0:8
小结:
1、KVO
生成的子类NSKVONotifying_Person
多了四个实例方法
setAge:
: 参数为int
,返回值为void
class
: 没有参数,返回值为#
,表示class
,也就是重写了class
方法_isKVOA
: 没有参数,返回值类型为BOOL
dealloc
: 重写了dealloc
方法
2、原来的类Person
有两个属性的setter
和getter
方法setAge:
age
unsigned int classMethodCount = 0;
// 使用元类
Method *classMethods = class_copyMethodList(objc_getMetaClass(class_getName([self.person class])), &classMethodCount);
NSLog(@"classMethodCount: %d", classMethodCount);
for (int m = 0; m < classMethodCount; m++) {
Method method = classMethods[m];
const char *name = sel_getName(method_getName(method));
const char *typeEncoding = method_getTypeEncoding(method);
NSLog(@"分割线 --------------");
NSLog(@"methodName: %s", name);
NSLog(@"methodTypeEncoding: %s", typeEncoding);
}
输出结果如下:
classMethodCount: 0
小结:
1、KVO生成的子类NSKVONotifying_Person
没有类方法
2、原来的类Person
没有类方法
总结
原来的类Person
|
KVO 生成的子类 NSKVONotifying_Person
|
|
---|---|---|
class (对象方法) |
Person |
Person |
class (类方法) |
Person |
NSKVONotifying_Person |
isa (对象) |
NSKVONotifying_Person |
NSKVONotifying_Person |
superclass |
NSObject |
Person |
成员变量Ivar
|
_age |
没有 |
属性property
|
age |
没有 |
实例方法method
|
setAge: age
|
setAge: class _isKVOA dealloc
|
类方法method
|
没有 | 没有 |
假设需要监听的类为MyObject
:
-
KVO
会自动创建一个继承于原来的类MyObject
的子类NSKVONotifying_MyObject
(runtime动态创建一个类) - 把原来的类的对象
isa
指针指向新建的子类NSKVONotifying_MyObject
(runtime修饰isa指针的函数) - 子类
NSKVONotifying_MyObject
会添加方法_isKVOA
,返回值YES
表示是KVO
监听的类 - 子类
NSKVONotifying_MyObject
会添加方法class
,返回值MyObject
表示是对象所属于的类MyObject
- 子类会重写监听的属性的
setter
方法(setAgeForNone:
),方法内部调用的是Foundation
框架中的_NSSetIntValueAndNotify
函数 -
set
值之前调用willChangeValueForKey:
-
set
值之后调用didChangeValueForKey:
参考文章
[https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177-BCICJDHA]
(https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177-BCICJDHA)