KVO:Key-value observer,也就是鍵值觀察返劲,是Objective-C對觀察者模式的實現(xiàn),每當被觀察對象的某個屬性值發(fā)生改變時栖茉,注冊的觀察者便能得到通知篮绿。
當然想了解KVO,還要先對KVC有所了解:KVC底層原理衡载,本文利用Runtime實現(xiàn)自定義KVO搔耕,如果對Runtime不熟悉可以先了解下前幾篇文章:Runtime底層原理。KVO-官網(wǎng)直通車
先簡單介紹一下KVO使用:
- 添加觀察:
addObserver:self forKeyPath:options:context:
- 觀察回調(diào):
observeValueForKeyPath:ofObject :change: context:
- 移除觀察:
removeObserver: forKeyPath:
TIP:建議KVO還是手動添加移除。如果沒有移除觀察弃榨,會有隱藏奔潰隱患(單例)菩收,比如當觀察者析構(gòu)時不會自動移除,被觀察對象繼續(xù)發(fā)送消息, 像發(fā)送一個消息給已經(jīng)釋放的對象, 觸發(fā)exception鲸睛。
KVO原理:
KVO默認觀察setter娜饵,使用isa-swizzling
來實現(xiàn)自動鍵值觀察,也就是被觀察對象的isa會被修改官辈,指向一個動態(tài)生成的子類NSKVONotifying_xxxx(isa在移除觀察者之后復(fù)原箱舞,動態(tài)生成的類不會被移除),但是通過object_getClass
獲取的還是原來的類拳亿,該子類重寫了觀察對象的setter方法晴股,還有class
、dealloc
方法和_isKVOA
標識肺魁,并在重寫setter方法中調(diào)用– willChangeValueForKey
和– didChangeValueForKey
电湘,然后向父類發(fā)送消息。如果automaticallyNotifiesObserversForKey
返回NO的時候可以手動觀察
- 動態(tài)生成子類: NSKVONotifying_xxxx鹅经,用原來的類名做后綴
- 重寫觀察對象的setter寂呛,
class
、dealloc
方法和_isKVOA
標識 - 在重寫setter方法中調(diào)用 – willChangeValueForKey和 – didChangeValueForKey
- 向父類發(fā)送消息
自定義KVO
知道了KVO的原理后我們利用Runtime進行驗證并自定義KVO的實現(xiàn)瘾晃,在實現(xiàn)了系統(tǒng)KVO的功能基礎(chǔ)上還添加了自動移除觀察者機制贷痪、監(jiān)聽利用block回調(diào)等。
利用LLDB查看isa的指針蹦误,再利用Runtime查看添加觀察前后的變化劫拢,可以通過下面的方法對原來的類和新增的NSKVONotifying_xxxx類進行對比
// 遍歷方法 -- 判斷imp指針是否改變也就是重寫
- (void)getClassAllMethod:(Class)cls {
if (!cls) return;
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i<count; i++) {
Method method = methodList[i];
SEL sel = method_getName(method);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@ --- %p",NSStringFromSelector(sel), imp);
}
free(methodList);
}
// 遍歷屬性
- (void)getClassProperty:(Class)cls {
if (!cls) return;
//獲取類中的屬性列表
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList(cls, &propertyCount);
for (int i = 0; i<propertyCount; i++) {
NSLog(@"屬性的名稱為 : %s",property_getName(properties[i]));
/**
特性編碼 具體含義
R readonly
C copy
& retain
N nonatomic
G(name) getter=(name)
S(name) setter=(name)
D @dynamic
W weak
P 用于垃圾回收機制
*/
NSLog(@"屬性的特性字符串為: %s",property_getAttributes(properties[i]));
}
//釋放屬性列表數(shù)組
free(properties);
}
// 遍歷變量
- (void)getClassAllIvar:(Class)cls {
if (!cls) return;
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(cls, &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivarList[i];
NSLog(@"%s",ivar_getName(ivar));
}
free(ivarList);
}
// 遍歷類以及子類
- (void)getClasses:(Class)cls {
if (!cls) return;
// 注冊類的總數(shù)
int count = objc_getClassList(NULL, 0);
// 創(chuàng)建一個數(shù)組,其中包含給定對象
NSMutableArray *mArr = [NSMutableArray arrayWithObject:cls];
// 獲取所有已注冊的類
Class *classes = (Class *)malloc(sizeof(Class)*count);
objc_getClassList(classes, count);
for (int i = 0; i < count; i++) {
if (cls == class_getSuperclass(classes[i])) {
[mArr addObject:classes[i]];
}
}
free(classes);
NSLog(@"classes --- %@", mArr);
}
經(jīng)過驗證后開始自定義KVO實現(xiàn)系統(tǒng)功能胖缤,并額外加上自定義的一些功能尚镰。先添加用來保存KVO信息的Info類VKVOInfo
用來保存信息,還有一個擴展NSObject+VKVO
哪廓,主要實現(xiàn)系統(tǒng)的原有功能狗唉,再添加自定義的一些方法,比如自動移除觀察者等涡真。
- 首先先動態(tài)生成子類分俯,并添加
setter
,class
哆料、dealloc
方法
#pragma mark - 動態(tài)生成子類
- (Class)createChildClass:(NSString *)keyPath {
NSString *oldName = NSStringFromClass([self class]);
NSString *newName = [NSString stringWithFormat:@"%@%@", kVKVOPrefix, oldName];
Class newClass = NSClassFromString(newName);
// 如果內(nèi)存不存在,創(chuàng)建生成新的類缸剪,防止重復(fù)創(chuàng)建生成新類
if (newClass) return newClass;
newClass = objc_allocateClassPair([self class], newName.UTF8String, 0);
objc_registerClassPair(newClass);
// 添加class方法
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classType = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)v_class, classType);
// 添加setter方法
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterType = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)v_setter, setterType);
// 添加dealloc方法
SEL deallocSEL = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
const char *deallocType = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSEL, (IMP)v_dealloc, deallocType);
return newClass;
}
- 把isa指針指向動態(tài)生成的KVONotifying子類(Person類會動態(tài)生成KVONotifying_Person)
object_setClass(self, newClass);
- 保存KVO的信息
VKVOInfo *KVOInfo = [[VKVOInfo alloc] initWithObserver:observer forKeyPath:keyPath options:options handleBlock:handleBlock];
NSMutableArray *infoArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kVKVOAssiociateKey));
if (!infoArr) {
infoArr = [NSMutableArray arrayWithCapacity:1];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kVKVOAssiociateKey), infoArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[infoArr addObject:KVOInfo];
下面是部分重要代碼:在setter
方法中瘫证,先將消息發(fā)送給原來的類棵逊,再利用block響應(yīng)回調(diào)(這里也可以添加判斷,利用block回調(diào)或者設(shè)置代理)乐导,也可以添加一些自定義的方法,比如去掉NSKeyValueObservingOptions參數(shù)奋渔。
static void v_setter(id self, SEL _cmd, id newValue) {
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
/// Specifies the superclass of an instance.
struct objc_super v_objc_super = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// 消息轉(zhuǎn)發(fā)給父類
void (*v_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
v_msgSendSuper(&v_objc_super, _cmd, newValue);
// 響應(yīng)回調(diào)
NSMutableArray *infoArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kVKVOAssiociateKey));
for (VKVOInfo *info in infoArr) {
if ([info.keyPath isEqualToString:keyPath]) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
if (info.options & NSKeyValueObservingOptionNew) {
if (info.handleBlock) {
info.handleBlock(info.observer, info.keyPath, info.options, newValue, oldValue);
}
}
// SEL obserSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
// void (*v_objc_msgSend)(id, SEL, id, id, id, void *) = (void *)objc_msgSend;
// Class supperClass = (object_getClass(self));
// v_objc_msgSend(info.observer, obserSEL, keyPath, supperClass, @{keyPath:newValue}, NULL);
});
}
}
}
這里是Demo地址:https://github.com/JBWangWork/VCustomKVO镊逝,本Demo已更新,去掉了options和context參數(shù)(系統(tǒng)context可以起到快速定位觀察鍵的作用)嫉鲸。本Demo只適用于學習KVO底層原理撑蒜。
該文章為記錄本人的學習路程,也希望能夠幫助大家玄渗,知識共享座菠,共同成長,共同進步L偈鳌T〉巍!文章地址:http://www.reibang.com/p/724f6ab39400