[TOC]
KVO
研究
沒有使用KVO和使用KVO的變化
測(cè)試的類Person
@interface Person : NSObject
@property (nonatomic, assign) int age;
@end
@implementation Person
@end
通過 objc_copyClassList
驗(yàn)證
思路: 使用runmtime
的 objc_copyClassList
獲取所有的類浅侨,對(duì)比前后的變化
未使用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
實(shí)際上會(huì)打印很多類盔腔,上面只截取了相關(guān)的類
使用了KVO (注意:這里需要先添加監(jiān)聽竹椒,然后再使用objc_copyClassList
方法獲取岛啸,否則是獲取不到的,因?yàn)?code>KVO是動(dòng)態(tài)添加的)
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
可以看到多了一個(gè)類 NSKVONotifying_Person
通過 isa
來驗(yàn)證
- 添加
kvo
前后isa
指向:- 之前:
Person
- 之后:
NSKVONotifying_Person
- 之前:
- 添加
kvo
前后po
命令輸出的都是Person
小結(jié):
1掌动、使用KVO
讹堤,runtime
會(huì)給需要監(jiān)聽的類Person
自動(dòng)創(chuàng)建一個(gè)類NSKVONotifying_Person
2、并且把原來類創(chuàng)建的對(duì)象的isa
指向了新建的類NSKVONotifying_Person
通過 objc
源碼可以看到 Class changeIsa(Class newCls);
這個(gè)函數(shù)來改變 isa
自動(dòng)創(chuàng)建的類 NSKVONotifying_Person
的一些特性
使用objc_getClass
直接獲取類
父類/元類
測(cè)試代碼如下:
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);
}
輸出結(jié)果如下:
添加KVO之前 ======
objClass: Person
metaclass: Person
superClass: NSObject
superMetaClass: NSObject
添加KVO之后 ======
objClass: NSKVONotifying_Person
metaclass: NSKVONotifying_Person
superClass: Person
superMetaClass: Person
小結(jié):
因此KVO就是:
1牌捷、通過運(yùn)行時(shí)runtime
新建了一個(gè)繼承于原來類(Person)的子類(NSKVONotifying_Person)
2墙牌、并且把原來類創(chuàng)建的對(duì)象的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
小結(jié):
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
小結(jié):
KVO
生成的子類NSKVONotifying_Person
,沒有添加額外的屬性
實(shí)例方法
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
小結(jié):
1暗甥、KVO
生成的子類NSKVONotifying_Person
多了四個(gè)實(shí)例方法
setAge:
: 參數(shù)為int
喜滨,返回值為void
,父類Person
也有這個(gè)方法淋袖,子類重寫class
: 沒有參數(shù)鸿市,返回值為#
锯梁,表示class
即碗,也就是重寫了class
方法_isKVOA
: 沒有參數(shù),返回值類型為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);
}
}
輸出結(jié)果為:
-
_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);
}
結(jié)果如下:
- 使用
KVO
之后setter
方法的實(shí)現(xiàn)改變了 (Foundation`_NSSetIntValueAndNotify)
dealloc
方法
最后理一下原理:
小結(jié):
KVO
生成的子類NSKVONotifying_Person
會(huì)重寫或添加了一些方法
1剥懒、是否是KVO監(jiān)聽類的方法_isKVOA
: 返回值是布爾類型B
(表示BOOL
),值為YES
2合敦、重寫了需要監(jiān)聽的屬性的setter
方法初橘,參數(shù)是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
小結(jié):
KVO生成的子類NSKVONotifying_Person
沒有類方法
斷點(diǎn)查看
-
KVO
生成的子類NSKVONotifying_Person
:-
isa
:NSKVONotifying_Person
-
class
方法:NSKVONotifying_Person
-
-
KVO
原來的類Person
-
isa
:NSKVONotifying_Person
崔梗,這個(gè)是實(shí)際的類 -
class
方法:Person
夜只,返回的是原來的類Person
,讓我們誤以為還是原來的類蒜魄,其實(shí)不是的扔亥,通過isa
可以看到
-
屬性值變化的監(jiān)聽
NSLog(@"---");
obj.ageForNone = 10;
NSLog(@"1111111");
obj.ageForNone = 20;
NSLog(@"2222222");
obj.ageForNone = 20;
// 監(jiān)聽方法
- (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;
}
使用斷點(diǎn)進(jìn)入查看如下:
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);
}
輸出結(jié)果如下:
count: 1
======
name: _age
offset: 8
type: i
小結(jié)
1、KVO
生成的子類NSKVONotifying_Person
沒有額外的成員變量
2谈为、原來的類Person
有一個(gè)成員變量_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);
}
輸出結(jié)果如下:
propertyCount: 1
======
propertyAttributes: Ti,N,V_age
propertyName: age
小結(jié):
1旅挤、KVO
生成的子類NSKVONotifying_Person
,沒有添加額外的屬性
2伞鲫、原來的類Person
有一個(gè)屬性age
實(shí)例方法
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);
}
輸出結(jié)果如下:
methodCount: 2
分割線 --------------
methodName: setAge:
methodTypeEncoding: v20@0:8i16
分割線 --------------
methodName: age
methodTypeEncoding: i16@0:8
小結(jié):
1粘茄、KVO
生成的子類NSKVONotifying_Person
多了四個(gè)實(shí)例方法
setAge:
: 參數(shù)為int
,返回值為void
class
: 沒有參數(shù)秕脓,返回值為#
驹闰,表示class
,也就是重寫了class
方法_isKVOA
: 沒有參數(shù)撒会,返回值類型為BOOL
dealloc
: 重寫了dealloc
方法
2嘹朗、原來的類Person
有兩個(gè)屬性的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);
}
輸出結(jié)果如下:
classMethodCount: 0
小結(jié):
1、KVO生成的子類NSKVONotifying_Person
沒有類方法
2诵肛、原來的類Person
沒有類方法
總結(jié)
原來的類Person
|
KVO 生成的子類 NSKVONotifying_Person
|
|
---|---|---|
class (對(duì)象方法) |
Person |
Person |
class (類方法) |
Person |
NSKVONotifying_Person |
isa (對(duì)象) |
NSKVONotifying_Person |
NSKVONotifying_Person |
superclass |
NSObject |
Person |
成員變量Ivar
|
_age |
沒有 |
屬性property
|
age |
沒有 |
實(shí)例方法method
|
setAge: age
|
setAge: class _isKVOA dealloc
|
類方法method
|
沒有 | 沒有 |
假設(shè)需要監(jiān)聽的類為MyObject
:
-
KVO
會(huì)自動(dòng)創(chuàng)建一個(gè)繼承于原來的類MyObject
的子類NSKVONotifying_MyObject
(runtime動(dòng)態(tài)創(chuàng)建一個(gè)類) - 把原來的類的對(duì)象
isa
指針指向新建的子類NSKVONotifying_MyObject
(runtime修飾isa指針的函數(shù)) - 子類
NSKVONotifying_MyObject
會(huì)添加方法_isKVOA
屹培,返回值YES
表示是KVO
監(jiān)聽的類 - 子類
NSKVONotifying_MyObject
會(huì)添加方法class
默穴,返回值MyObject
表示是對(duì)象所屬于的類MyObject
- 子類會(huì)重寫監(jiān)聽的屬性的
setter
方法(setAgeForNone:
),方法內(nèi)部調(diào)用的是Foundation
框架中的_NSSetIntValueAndNotify
函數(shù) -
set
值之前調(diào)用willChangeValueForKey:
-
set
值之后調(diào)用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)