KVO原理分析及使用进阶
该文章属于<简书 — 刘小壮>原创,转载请注明:<简书 — 刘小壮> https://www.jianshu.com/p/badf5cac0130 我们在工作中经常会用到
概述
基础使用使用
注册方法在注册观察者时,可以传入 还可以通过方法 在调用 监听方法观察者需要实现
如果被观察对象是集合对象,在 兼容的调用方式调用 // 直接调用set方法,或者通过属性的点语法间接调用 [account setName:@"Savings"]; // 使用KVC的setValue:forKey:方法 [account setValue:@"Savings" forKey:@"name"]; // 使用KVC的setValue:forKeyPath:方法 [document setValue:@"Savings" forKeyPath:@"account.name"]; // 通过mutableArrayValueForKey:方法获取到代理对象,并使用代理对象进行操作 Transaction *newTransaction = <#Create a new transaction for the account#>; NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"]; [transactions addObject:newTransaction]; 实际应用
注意点
苹果官方推荐的方式是,在 手动调用KVO
- (void)setBalance:(double)theBalance { if (theBalance != _balance) { [self willChangeValueForKey:@"balance"]; _balance = theBalance; [self didChangeValueForKey:@"balance"]; } } 可以看到调用 如果想控制当前对象的自动调用过程,也就是由上面两个方法发起的 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey { BOOL automatic = NO; if ([theKey isEqualToString:@"balance"]) { automatic = NO; } else { automatic = [super automaticallyNotifiesObserversForKey:theKey]; } return automatic; } 实现原理
测试代码为了测试 @interface KVOObject : NSObject @property (nonatomic,copy ) NSString *name; @property (nonatomic,assign) NSInteger age; @end @implementation KVOObject - (NSString *)description { NSLog(@"object address : %p n",self); IMP nameIMP = class_getMethodImplementation(object_getClass(self),@selector(setName:)); IMP ageIMP = class_getMethodImplementation(object_getClass(self),@selector(setAge:)); NSLog(@"object setName: IMP %p object setAge: IMP %p n",nameIMP,ageIMP); Class objectMethodClass = [self class]; Class objectRuntimeClass = object_getClass(self); Class superClass = class_getSuperclass(objectRuntimeClass); NSLog(@"objectMethodClass : %@,ObjectRuntimeClass : %@,superClass : %@ n",objectMethodClass,objectRuntimeClass,superClass); NSLog(@"object method list n"); unsigned int count; Method *methodList = class_copyMethodList(objectRuntimeClass,&count); for (NSInteger i = 0; i < count; i++) { Method method = methodList[i]; NSString *methodName = NSStringFromSelector(method_getName(method)); NSLog(@"method Name = %@n",methodName); } return @""; } 在另一个类中分别创建两个 @property (nonatomic,strong) KVOObject *object1; @property (nonatomic,strong) KVOObject *object2; self.object1 = [[KVOObject alloc] init]; self.object2 = [[KVOObject alloc] init]; [self.object1 description]; [self.object2 description]; [self.object1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [self.object1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [self.object1 description]; [self.object2 description]; self.object1.name = @"lxz"; self.object1.age = 20; 下面是 // 第一次 object address : 0x604000239340 object setName: IMP 0x10ddc2770 object setAge: IMP 0x10ddc27d0 objectMethodClass : KVOObject,ObjectRuntimeClass : KVOObject,superClass : NSObject object method list method Name = .cxx_destruct method Name = description method Name = name method Name = setName: method Name = setAge: method Name = age object address : 0x604000237920 object setName: IMP 0x10ddc2770 object setAge: IMP 0x10ddc27d0 objectMethodClass : KVOObject,superClass : NSObject object method list method Name = .cxx_destruct method Name = description method Name = name method Name = setName: method Name = setAge: method Name = age // 第二次 object address : 0x604000239340 object setName: IMP 0x10ea8defe object setAge: IMP 0x10ea94106 objectMethodClass : KVOObject,ObjectRuntimeClass : NSKVONotifying_KVOObject,superClass : KVOObject object method list method Name = setAge: method Name = setName: method Name = class method Name = dealloc method Name = _isKVOA object address : 0x604000237920 object setName: IMP 0x10ddc2770 object setAge: IMP 0x10ddc27d0 objectMethodClass : KVOObject,superClass : NSObject object method list method Name = .cxx_destruct method Name = description method Name = name method Name = setName: method Name = setAge: method Name = age 我们发现对象被 在上面的代码中还发现了 重写setter方法
object_getClass为什么上面调用 调用 由此可以推测, typedef struct objc_class *Class; struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; #endif } 缺点苹果提供的 在调用 NSStringFromSelector(@selector(isFinished))
自己实现KVO除了上面的缺点, self.object1 = [[KVOObject alloc] init]; [self.object1 lxz_addObserver:self originalSelector:@selector(name) callback:^(id observedObject,NSString *observedKey,id oldValue,id newValue) { // callback }]; self.object1.name = @"lxz"; [self.object1 lxz_removeObserver:self originalSelector:@selector(name)]; 调用代码很简单,直接通过 通过 如果重复 注意需要注意的是, KVOController想在项目中安全便捷的使用 源码分析从源码来看还是比较简单的,主要分为
在 - (FBKVOController *)KVOControllerNonRetaining { id controller = objc_getAssociatedObject(self,NSObjectKVOControllerNonRetainingKey); if (nil == controller) { controller = [[FBKVOController alloc] initWithObserver:self retainObserved:NO]; self.KVOControllerNonRetaining = controller; } return controller; } FBKVOController部分在 还有一个私有类 其功能很简单,通过 在外界使用时需要用 使用 - (void)_observe:(id)object info:(_FBKVOInfo *)info { NSMutableSet *infos = [_objectInfosMap objectForKey:object]; _FBKVOInfo *existingInfo = [infos member:info]; if (nil != existingInfo) { return; } if (nil == infos) { infos = [NSMutableSet set]; [_objectInfosMap setObject:infos forKey:object]; } [infos addObject:info]; [[_FBKVOSharedController sharedController] observe:object info:info]; } 因为 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |