ios 学习

iOS KVO 自己实现(利用Runtime)

2018-11-19  本文已影响1人  羽裳有涯

一、KVO 实现机制

Apple 的文档有简单提到过 KVO 的实现


Key-Value Observing Implementation Details

Automatic key-value observing is implemented using a technique called *isa-swizzling*.

The `isa` pointer, as the name suggests, points to the object's class which maintains a dispatch table. 
This dispatch table essentially contains pointers to the methods the class implements, among other data.

When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, 
pointing to an intermediate class rather than at the true class. 
As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.

  1. 观察一个对象时,一个新的类会动态被创建;
  2. 这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法;
  3. 重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改;
  4. 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类;
  5. 完成新创建的子类的实例。

二、KVO 缺陷

注:有不少人都觉得官方 KVO 不好使的。

  1. Mike Ash 的 Key-Value Observing Done Right
  2. 以及获得不少分享讨论的 KVO Considered Harmful
  3. 所以在实际开发中 KVO 使用的情景并不多,更多时候还是用 Delegate 或 NotificationCenter。

三、自己实现 KVO

- (void)PG_addObserver:(NSObject *)observer
                forKey:(NSString *)key
             withBlock:(PGObservingBlock)block
{
    SEL setterSelector = NSSelectorFromString(setterForGetter(key));
    Method setterMethod = class_getInstanceMethod([self class], setterSelector);
    if (!setterMethod) {
        NSString *reason = [NSString stringWithFormat:@"Object %@ does not have a setter for key %@", self, key];
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:reason
                                     userInfo:nil];
        
        return;
    }
    
    Class clazz = object_getClass(self);
    NSString *clazzName = NSStringFromClass(clazz);
    
    // if not an KVO class yet
    if (![clazzName hasPrefix:kPGKVOClassPrefix]) {
        clazz = [self makeKvoClassWithOriginalClassName:clazzName];
        object_setClass(self, clazz);
    }
    
    // add our kvo setter if this class (not superclasses) doesn't implement the setter?
    if (![self hasSelector:setterSelector]) {
        const char *types = method_getTypeEncoding(setterMethod);
        class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
    }
    
    PGObservationInfo *info = [[PGObservationInfo alloc] initWithObserver:observer Key:key block:block];
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
    if (!observers) {
        observers = [NSMutableArray array];
        objc_setAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [observers addObject:info];
}
  1. 检查对象的类有没有相应的 setter 方法。如果没有抛出异常;
  2. 检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类;
  3. 检查对象的 KVO 类重写过没有这个 setter 方法。如果没有,添加重写的 setter 方法;
  4. 添加这个观察者
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
{
   NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
   Class clazz = NSClassFromString(kvoClazzName);
   
   if (clazz) {
       return clazz;
   }
   
   // class doesn't exist yet, make it
   Class originalClazz = object_getClass(self);
   Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
   
   // grab class method's signature so we can borrow it
   Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
   const char *types = method_getTypeEncoding(clazzMethod);
   class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
   
   objc_registerClassPair(kvoClazz);
   
   return kvoClazz;
}
static void kvo_setter(id self, SEL _cmd, id newValue)
{
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = getterForSetter(setterName);
    
    if (!getterName) {
        NSString *reason = [NSString stringWithFormat:@"Object %@ does not have setter %@", self, setterName];
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:reason
                                     userInfo:nil];
        return;
    }
    
    id oldValue = [self valueForKey:getterName];
    
    struct objc_super superclazz = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    
    // cast our pointer so the compiler won't complain
    void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;
    
    // call super's setter, which is original class's setter method
    objc_msgSendSuperCasted(&superclazz, _cmd, newValue);
    
    // look up observers and call the blocks
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
    for (PGObservationInfo *each in observers) {
        if ([each.key isEqualToString:getterName]) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                each.block(self, getterName, oldValue, newValue);
            });
        }
    }
}
@interface PGObservationInfo : NSObject

@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) PGObservingBlock block;

@end

参考:
完整的例子可以从这里下载:ImplementKVO
KVO Implementation

Creating Classes at Runtime in Objective-C

Key-Value Observing Done Right

By your command

Associated Objects

上一篇下一篇

猜你喜欢

热点阅读