最近看了下iOS的KVO的源码实现,在这里Apple已经把runtime用到了极致,感觉到runtime的强大之处。由于时间有限,里面的很多实现细节没有仔细阅读,以后抽时间一一分析。现在先来说一下KVO的实现机制。 比如有如下类:

@interface MyObject : NSObject
@property (nonatomic, strong) NSString *name;
@end

@implementation MyObject

@end

在某个类中有如下代码:

obj = [[MyObject alloc] init];
[obj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

当调用到最后一个方法时,在内部已经生成了一个子类,obj的isa指向新生成的子类,MyObject是新生成子类的父类,具体结构如下图所示: kvo_memory 这时KVO_MyObject通过runtime重写了setName:方法。实现伪码如下:

//KVO_MyObject Class
- (void) setName: (NSString *)val
{
  NSString	*key;
  Class		c = [self class];
  void		(*imp)(id,SEL,NSString *);

  imp = (void (*)(id,SEL,NSString *))[c instanceMethodForSelector: _cmd];

  if ([c automaticallyNotifiesObserversForKey: @"setName"] == YES)
    {
      // pre setting code here
      [self willChangeValueForKey: key];
      (*imp)(self, _cmd, val);
      // post setting code here
      [self didChangeValueForKey: key];
    }
  else
    {
      (*imp)(self, _cmd, val);
    }
}

那么这段源码有没有什么问题?

Class c = [self class]返回结果是KVO_MyObject,然后[c instanceMethodForSelector: _cmd]返回的又是自己的实现方法,然后再调用(*imp)(self, _cmd, val);,这样就会陷入一个死循环。

那么Apple是如何解决这个问题的?很简单,KVO_MyObject里面重写了- (Class) class方法。看源码:

//KVO_MyObject
- (Class) class
{
  return class_getSuperclass(object_getClass(self));
}

这样当上面再次调用[self class]时,其实返回的类就是MyObject了,返回的imp也是MyObject中的真正的imp了,这样就解决了上面的那个问题。

分析到此为止,细节实现可以参考源码。下载地址