KVO底层实现原理,仿写KVO
这篇文章简单介绍苹果的KVO底层是怎么实现的,自己仿照KVO的底层实现,写一个自己的KVO监听
#pragma mark--KVO底层实现
第一步:新建一个Person类继承NSObject
Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject //字符串类型的属性name @property (nonatomic, strong) NSString *name; @end
Person.m
#import "Person.h" @implementation Person - (void)setName:(NSString *)name { //别问为什么(下面有用处),就是要自己处理set方法 _name = [NSString stringWithFormat:@"%@aaaa",name]; } @end
第二步:在控制器中创建一个Person类型的对象p,利用苹果的KVO来监听该对象p的name属性的变化
ViewController.h
#import <UIKit/UIKit.h> @interface ViewController : UIViewController @end
ViewController.m
#import "ViewController.h" #import "Person.h" @interface ViewController () @property (nonatomic, strong) Person *p; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Person *p = [[Person alloc] init]; // 监听name属性有没有改变 [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; self.p = p; } //点击屏幕修改self.p的name属性的值 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { static int i = 0; i++; self.p.name = [NSString stringWithFormat:@"%d",i]; } // 所有的KVO监听都会来到这个方法 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { NSLog(@"%@",self.p.name); } @end
会打印1aaaa,2aaaa,3aaaa...... //从打印可以看出KVO监听的是set方法的调用!!!
实际上:KVO的本质就是监听一个对象有没有调用set方法!!!
怎么验证呢?
1.将Person.h中的代码修改为
#import <Foundation/Foundation.h> @interface Person : NSObject { @public NSString *_name;//为了验证KVO监听的是setter方法 } @end
将ViewController.m的viewDidLoad中的代码修改为
- (void)viewDidLoad { [super viewDidLoad]; Person *p = [[Person alloc] init]; // 监听_name有没有改变 [p addObserver:self forKeyPath:@"_name" options:NSKeyValueObservingOptionNew context:nil]; self.p = p; }
运行后,点击屏幕是没有任何打印的-->结论1:KVO的本质就是监听一个对象有没有调用set方法!!!
第三步:打断点看一下addObserver 到底干了什么事情?
继续走一步:
这个NSKVONotifying_Person类是什么鬼?
估计你已经猜到了! 没错这个NSKVONotifying_Person类就是系统帮我们实现的!!!
怎么验证呢? 想到了吗?我们自己把这个类给重写了!看看会发生什么?
第四步:创建NSKVONotifying_Person类
NSKVONotifying_Person.h
#import <Foundation/Foundation.h> @interface NSKVONotifying_Person : NSObject @end
NSKVONotifying_Person.m
#import "NSKVONotifying_Person.h" @implementation NSKVONotifying_Person @end
第五步运行:
恭喜猜测没错,验证通过!!!
结论2:系统创建了一个NSKVONotifying_XXX的类
结论3:修改了对象p的isa指针
那么系统帮我们创建的NSKVONotifying_XXX类有没有继承自我们自己常见的XXX类呢?
我既然这么问,肯定是继承啦!!因为我们已经把属性对应的set方法给重写了!!!因为已经修改了对象的指针(用户调用对象p的方法就不是Person的方法了,是isa指针指向的类的对应的方法),如果不继承的话,相当于把用户重写的set方法给覆盖了,用户调用自己写的set方法就不起作用了!!!也就有了 结论4:重写对应的set方法再内部实现父类做法,通知观察者
KVO底层实现结论:(都是系统自动帮我们实现的)
1> 创建NSKVONotifying_XXX的类
2> 重写对应属性的set方法,在内部实现父类做法,通知观察者
3> 修改当前对象的isa指针,指向创建的NSKVONotifying_XXX(这样实际调用的时候就会走NSKVONotifying_XXX类中对应的方法)
#pragma mark--仿写KVO实现
知道了KVO底层实现的原理就可以仿照KVO写一个自己的"KVO"了
步骤一:给NSObject写一个分类
NSObject+KVO.h
#import <Foundation/Foundation.h> @interface NSObject (KVO) //添加监听 - (void)ey_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; //移除监听 - (void)ey_removeObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath context:(nullable void *)context; @end
NSObject+KVO.m
// // NSObject+KVO.m // KVO底层实现 // // Created by lieryang on 2017/6/17. // Copyright © 2017年 lieryang. All rights reserved. // #import "NSObject+KVO.h" #import <objc/message.h> static NSString * const EYKVONotifying_ = @"EYKVONotifying_"; static NSString * const observerKey = @"observer"; @implementation NSObject (KVO) //添加监听 - (void)ey_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context { if (keyPath.length == 0) {//如果传进来的keyPath为@""或者为nil 直接返回 return; } // 1. 检查对象的类有没有相应的 setter 方法。 SEL setterSelector = NSSelectorFromString([self setterForGetter:keyPath]); Method setterMethod = class_getInstanceMethod([self class], setterSelector); if (!setterMethod) {//如果没有直接返回,不需要任何处理 NSLog(@"找不到该方法"); return; } // 2. 检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类 Class clazz = object_getClass(self); NSString *className = NSStringFromClass(clazz); if (![className hasPrefix:EYKVONotifying_]) { clazz = [self ey_KVOClassWithOriginalClassName:className]; object_setClass(self, clazz); } // 3. 为EYKVONotifying_XXX添加setter方法的实现 const char *types = method_getTypeEncoding(setterMethod); class_addMethod(clazz, setterSelector, (IMP)ey_setter, types); // 4. 添加该观察者到观察者列表中 NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey)); if (!observers) { observers = [NSMutableArray array]; objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } if ([observers indexOfObject:observer] == NSNotFound) { [observers addObject:observer]; } } //移除监听 - (void)ey_removeObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath context:(nullable void *)context { NSMutableArray* observers = objc_getAssociatedObject(self, (__bridge const void *)(observerKey)); [observers removeObject:observer]; } #pragma mark - 注册自己的EYKVONotifying_XXX - (Class)ey_KVOClassWithOriginalClassName:(NSString *)className { // 生成EYKVONotifying_XXX的类名 NSString *kvoClassName = [EYKVONotifying_ stringByAppendingString:className]; Class kvoClass = NSClassFromString(kvoClassName); // 如果EYKVONotifying_XXX已经被注册过了, 则直接返回 if (kvoClass) { return kvoClass; } // 如果EYKVONotifying_XXX不存在, 则创建这个类 Class originClass = object_getClass(self); kvoClass = objc_allocateClassPair(originClass, kvoClassName.UTF8String, 0); // 修改EYKVONotifying_XXX方法的实现, 学习Apple的做法, 隐瞒这个EYKVONotifying_XXX Method classMethod = class_getInstanceMethod(kvoClass, @selector(class)); const char *types = method_getTypeEncoding(classMethod); class_addMethod(kvoClass, @selector(class), (IMP)ey_class, types); // 注册EYKVONotifying_XXX objc_registerClassPair(kvoClass); return kvoClass; } Class ey_class(id self, SEL cmd) { Class clazz = object_getClass(self); // EYKVONotifying_XXX Class superClazz = class_getSuperclass(clazz); // origin_class return superClazz; // origin_class } /** * 重写setter方法, 新方法在调用原方法后, 通知每个观察者 */ static void ey_setter(id self, SEL _cmd, id newValue) { NSString *setterName = NSStringFromSelector(_cmd); NSString *getterName = [self getterForSetter:setterName]; if (!getterName) { NSLog(@"找不到getter方法"); } // 调用原类的setter方法 struct objc_super superClazz = { .receiver = self, .super_class = class_getSuperclass(object_getClass(self)) }; // 这里需要做个类型强转, 否则会报too many argument的错误 ((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue); // 找出观察者的数组 NSArray *observers = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey)); // 遍历数组 for (id observer in observers) { // 调用监听者observer的observeValueForKeyPath 方法,因为observer为id类型,所以就偷懒调用了系统的监听回调,要是自己定义方法,会报找方法的错误,可以在添加监听的时候,传进来一个block代码块,在此处回调block,更方便外界的调用 [observer observeValueForKeyPath:getterName ofObject:self change:nil context:nil]; } } #pragma mark - 生成对应的setter方法字符串 - (NSString *)setterForGetter:(NSString *)key { // 1. 首字母转换成大写 NSString * firstString = [[key substringToIndex:1] uppercaseString]; // 2. 剩下的字母 NSString * remainingString = [key substringFromIndex:1]; // 3. 最前增加set, 最后增加: setName: NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstString, remainingString]; return setter; } #pragma mark - 生成对应的getter方法字符串 - (NSString *)getterForSetter:(NSString *)key { // setName if (key.length <=0 || ![key hasPrefix:@"set"] || ![key hasSuffix:@":"]) { return nil; } // 移除set和: NSRange range = NSMakeRange(3, key.length - 4); NSString *getter = [key substringWithRange:range]; // 小写 NSString *firstString = [[getter substringToIndex:1] lowercaseString]; getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString]; return getter; } @end
外界使用
创建Person类
Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic, copy) NSString * name; @end
Person.m
#import "Person.h" @implementation Person @end
#import "ViewController.h" #import "Person.h" #import "NSObject+KVO.h" @interface ViewController () @property (nonatomic, strong) Person * p; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Person * p = [[Person alloc] init]; [p ey_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; self.p = p; } // 监听的回调 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { NSLog(@"%@", self.p.name); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; self.p.name = [NSString stringWithFormat:@"%u", arc4random_uniform(20)]; } @end
更多内容--> 博客导航 每周一篇哟!!!
有任何关于iOS开发的问题!欢迎下方留言!!!或者邮件lieryangios@126.com 虽然我不一定能够解答出来,但是我会请教iOS开发高手!!!解答您的问题!!!