接着上一篇文章 KVO原理,模仿官方自定义实现KVO
1.在addObserver代码执行后,系统动态生成了一个NSKVONotifying_Person类
2.Person的isa指针改变指向,指向了NSKVONotifying_Person类
3.NSKVONotifying_Person重写下面的
set方法(无法监听成员变量,只能监听set方法)、 也修改原来的成员变量的值,同时做回调
class方法 返回原来的Person类
4.removeObserver方法之后重新将根据残余KeyPath的数量调整isa指向
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,copy) NSString * nickName;
@end
#import "Person.h"
@implementation Person{
}
@end
#import <Foundation/Foundation.h>
typedef NS_OPTIONS(NSUInteger, KLKeyValueObservingOptions) {
KLKeyValueObservingOptionNew = 0x01,
KLKeyValueObservingOptionOld = 0x02
};
@interface KLKVOInfo : NSObject
@property(nonatomic,copy) NSString * keyPath;
@property(nonatomic,weak) NSObject * observer;
@property(nonatomic,assign) KLKeyValueObservingOptions options;
- (instancetype)initWithObsever:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(KLKeyValueObservingOptions)options;
@end
#import "KLKVOInfo.h"
@implementation KLKVOInfo
- (instancetype)initWithObsever:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(KLKeyValueObservingOptions)options{
if(self = [super init]){
_observer = observer;
_keyPath = keyPath;
_options = options;
}
return self;
}
@end
#import "KLKVOInfo.h"
@interface NSObject(KLKVO)
- (void)KL_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(KLKeyValueObservingOptions)options context:(nullable void *)context;
- (void)KL_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)kl_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
@end
#import "NSObject+KLKVO.h"
#import <objc/runtime.h>
#import <objc/message.h>
static const NSString * KLKVOClassPrefifix = @"KLKVONotifying_";
static const NSString * KLKVOAssciocateKey = @"KLKVOAssciocateKey";
@implementation NSObject(KLKVO)
- (void)KL_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(KLKeyValueObservingOptions)options context:(nullable void *)context{
//1.排除成员变量和属性\
[self setterMethodJudgeFromKeyPath:keyPath];
//2.kvo - isa_swiziling
Class newClass = [self createChildClassWithKeyPath:keyPath];
//3. isa指向
object_setClass(self, newClass);
//4.setter业务逻辑
//保存下观察者
//因为可能会有多个keypath需要观察 所以这里需要收集多个数据
KLKVOInfo * info = [[KLKVOInfo alloc]initWithObsever:observer forKeyPath:keyPath options:options];
NSMutableArray * infos = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(KLKVOAssciocateKey));
if(!infos){
infos = [NSMutableArray arrayWithCapacity:1];
}
[infos addObject:info];
//注意循环引用问题 vc -> self-> infos -> info -> observer(vc)
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(KLKVOAssciocateKey), infos, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)KL_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context{
NSMutableArray * infos = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(KLKVOAssciocateKey));
//判断是否还有观察的keypath,如果为0则改变isa指向恢复原来
for(KLKVOInfo * info in infos){
if([info.keyPath isEqualToString:keyPath]){
[infos removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(KLKVOAssciocateKey), infos, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
//指向还原\
if(infos.count <= 0){
Class superClass = [self class];
object_setClass(self,superClass);
}
}
//创建子类
- (Class)createChildClassWithKeyPath:(NSString*)keyPath{
Class superClass = [self class];
NSString * oldClassName = NSStringFromClass([self class]);
NSString * newClassName = [NSString stringWithFormat:@"%@%@",KLKVOClassPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
//2.1申请类
if(newClass){
return newClass;
}
newClass = objc_allocateClassPair(superClass, newClassName.UTF8String, 0);
//2.2添加方法
//set方法
SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSel);
const char * setterType = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSel, (IMP)kl_setter, setterType);
//class方法
SEL classSel = NSSelectorFromString(@"class");
Method classMethod = class_getClassMethod(superClass, classSel);
const char * classType = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSel, (IMP)kl_class, classType);
//dealloc方法
//2.3注册类
objc_registerClassPair(newClass);
return newClass;
}
//子类重写的IMP
static void kl_setter(id self,SEL _cmd,id newValue,id oldValue){
//判断自动开关
//核心 Person setter _cmd 父类发送消息
struct objc_super kl_objc_super;
kl_objc_super.receiver = self;
kl_objc_super.super_class = class_getSuperclass(object_getClass(self));
objc_msgSendSuper(&kl_objc_super,_cmd,newValue);
//向观察者发送 通知
NSString * keyPath = getterFromSetter(NSStringFromSelector(_cmd));
NSMutableArray * infos = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)KLKVOAssciocateKey);
for (KLKVOInfo * info in infos) {
if(![info.keyPath isEqualToString:keyPath])
{
break;
}
//observer不是self 而是add的时候添加的,但是此处正常方法无法取到,所以使用关联对象获取\
id observer = info.observer;
NSMutableDictionary<NSKeyValueChangeKey,id> * change = [NSMutableDictionary dictionaryWithCapacity:1];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//观察新值 旧值 判断
if(info.options & KLKeyValueObservingOptionNew){\
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
}
if(info.options & KLKeyValueObservingOptionOld){\
[change setObject:@"" forKey:NSKeyValueChangeOldKey];
if(oldValue){
[change setObject:oldValue forKey:NSKeyValueChangeOldKey];
}
}
//发送观察回调消息
SEL observeSEL = @selector(kl_observeValueForKeyPath:ofObject:change:context:);
objc_msgSend(observer, observeSEL,keyPath,self,change,NULL);
});
}
}
Class kl_class(id self,SEL _cmd){\
return class_getSuperclass(object_getClass(self));
}
//判断setter方法是否存在
- (void)setterMethodJudgeFromKeyPath:(NSString*)keyPath{
Class superClass = object_getClass(self);
SEL setterSelector = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSelector);
if(!setterMethod){
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"当前%@缺少setter方法",keyPath] userInfo:nil];
}
}
//根据getter获取setter方法名
static NSString* setterForGetter(NSString* getter){
if(getter.length <= 0){return nil;}
NSString * firstString = [[getter substringToIndex:1] uppercaseString];
NSString * leaveString = [getter substringFromIndex:1];
NSString * setterStr = [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
return setterStr;
}
//根据setter获取getter方法名
static NSString* getterFromSetter(NSString* setter){
if(setter.length <= 0){return nil;}
NSString * leaveString = [setter substringWithRange:NSMakeRange(4, setter.length-5)];
NSString * firstString = [setter substringWithRange:NSMakeRange(3, 1)].lowercaseString;
NSString * getterStr = [NSString stringWithFormat:@"%@%@",firstString,leaveString];
return getterStr;
}
@end