KVO实现原理剖析

最近看了一些关于ios runtime相关的资料,看到网上有人发的关于kvo的实现原理,刚好有时间自己研究了一遍,整理下分享给初学的朋友。

KVO的全称是Key-Value Observing,它实现了一种机制,对所关心的属性对象添加观察者,当属性值发生变化时会得到通知,我们可以对变化做相应的处理。看过设计模式的同学应该知道,这是一种典型的观察者模式。KVO的最大优点就是底层框架已经支持,开发人员不需要实现属性值发生变化时发送通知的方案,这样就大大减少开发的工作量。其次,KVO框架很强大,可以支持多个观察者观察同一属性,或者一个观察者监听不同属性。

KVO的使用比较简单,基本上都是三步:

1.  注册观察者

addObserver:forKeyPath:options:context:

2.观察者中实现

observeValueForKeyPath:ofObject:change:context:

3.移除观察者

removeObserver:forKeyPath:

使用方法比较简单,我们这里就不详细解释了,不懂的地方可以查阅sdk,接下来我们看看KVO的实现原理,这需要大家对  Objective-C的对象模型有一定了解 。

研究KVO的时候我们发现系统使用Objective-C 强大的runtime功能实现了这个功能。属性类class中并没有实现KVO通知的相关方案,而是在调用addObserver之后定义属性类的子类subclass,subclass里边实现了属性的setter方法,setter方法中实现发动通知的功能。然后subclass中实现class函数,还让返回属性类的class,再让属性类对象的isa指向subclass,这样就伪装成表面上看还是属性类自己实现的通知功能。通过原理我们可以看出, 必须使用属性方法或者setValue:forKey方法赋值才会发送通知,直接赋值是不会收到通知的。

接下来我们写个演示程序看看KVO是怎么实现的:

@interface ClassTest : NSObject
{
  int x;
  int y;
  int z;
}

@property (nonatomic, assign) int x;
@property (nonatomic, assign) int y;
@property (nonatomic, assign) int z;
@end
@implementation ClassTest
@synthesize x, y,z;

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
  if([keyPath isEqualToString:@"x"])
  {
    NSObject* new = [change objectForKey:@"new"];
    NSLog(@"new x is %@", new);
  }
  else
  {
    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  }
}
@end

定义ClassTest类,定义三个属性x, y, z。

static NSArray* classMethodList(Class c)
{
  NSMutableArray* array = [NSMutableArray arrayWithCapacity:5];
  unsigned int count = 0;
  Method* methodList = class_copyMethodList(c, &count);
  for(int i = 0; i < count; ++i)
  {
    SEL sel = method_getName(*(methodList+i));
    [array addObject:NSStringFromSelector(sel)];
  }
  free(methodList);
  return array;
}

定义classMethodList函数使用  Objective-C runtime函数遍历class,获得方法列表

static void printDescription(NSString* name, id obj)
{
    NSString* string = [NSString stringWithFormat:@"%@:%@\n\tclass %@\n\tobjclass %@\n\timplementmethod %@\n",
      name,
      obj,
      [obj class],
      object_getClass(obj),
      [classMethodList(object_getClass(obj)) componentsJoinedByString:@" , "]];
    printf("%s", [string UTF8String]);
}

定义printDescription函数打印对象的所有信息,包括函数class信息和运行时动态class信息,注意这里object_getClass(obj)和obj->isa是等价的,只不过Objective-C 2.0开始不支持直接调用isa。

int main(int argc, char * argv[])
{
    @autoreleasepool {
  ClassTest* x = [[ClassTest alloc] init];
  ClassTest* y = [[ClassTest alloc] init];
  ClassTest* xy = [[ClassTest alloc] init];
  ClassTest* control = [[ClassTest alloc] init];

  [x addObserver:x forKeyPath:@"x" options:NSKeyValueObservingOptionNew context:nil];
  [y addObserver:y forKeyPath:@"y" options:NSKeyValueObservingOptionNew context:nil];
  [xy addObserver:xy forKeyPath:@"x" options:NSKeyValueObservingOptionNew context:nil];
  [xy addObserver:xy forKeyPath:@"y" options:NSKeyValueObservingOptionNew context:nil];

  printDescription(@"x", x);
  printDescription(@"y", y);
  printDescription(@"xy", xy);
  printDescription(@"control", control);

  printf("Using NSObject method, normal setX is %p, overrite setX is %p\n", [control methodForSelector:@selector(setX:)], [x methodForSelector:@selector(setX:)]);
  printf("Using libobjc method, normal setX is %p, overrite setX is %p\n",
         class_getMethodImplementation(object_getClass(control), @selector(setX:)),
         class_getMethodImplementation(object_getClass(x), @selector(setX:)));

  return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

我们在main函数中定义ClassTest对象x, y, z, control,x添加对属性x的观察者,y添加对属性y的观察者,xy添加对属性x和属性y的观察者,control不添加任何观察者,然后通过printDescription打印对象的信息,最后打印setX函数的地址,我们看看最终的打印结果。

x:<ClassTest: 0x8951690>
  class ClassTest
  objclass NSKVONotifying_ClassTest
  implementmethod setY: , setX: , class , dealloc , _isKVOA
y:<ClassTest: 0x89516c0>
  class ClassTest
  objclass NSKVONotifying_ClassTest
  implementmethod setY: , setX: , class , dealloc , _isKVOA
xy:<ClassTest: 0x89516d0>
  class ClassTest
  objclass NSKVONotifying_ClassTest
  implementmethod setY: , setX: , class , dealloc , _isKVOA
control:<ClassTest: 0x89516e0>
  class ClassTest
  objclass ClassTest
  implementmethod z , x , setX: , y , setY: , setZ: , observeValueForKeyPath:ofObject:change:context:
Using NSObject method, normal setX is 0x4ae0, overrite setX is 0x1134526
Using libobjc method, normal setX is 0x4ae0, overrite setX is 0x1134526

从打印结果我们看到,实际上系统定了一个叫做  NSKVONotifying_ClassTest的子类,子类中实现了

setY: , setX: , class , dealloc , _isKVOA函数,这个  _isKVOA函数应该是个私有函数,用来判断是否kvo框架生成的类, x, y, xy对象的运行时类都指向 NSKVONotifying_ClassTest,通过class函数返回的类还是指向ClassTest,但是control对象的不管运行时类还是class函数返回的类都指向 ClassTest。这样就验证了系统是通过定义Classtest类的子类来实现属性方法发送通知的,系统很聪明,子类中并没有实现setZ方法,因为我们并没有对属性z添加观察者。

在看看最后两行打印的结果,control对象的setX函数地址和x对象的setX函数地址是不一样的,说明setX函数被重写了。看别人之前的文章,通过NSObject方法打印control和x的  setX函数 地址是一样的,  现在验证的结果地址却不一样,  和使用runtime方法打印的结果完全一致, 这个估计是新的系统底层做了修改,让使用NSObject的methodForSelector方法获得函数是子类的函数。

时间: 2024-08-01 02:15:37

KVO实现原理剖析的相关文章

通过子类实现KVO,浅析KVO底层原理

通过手动实现KVO,对KVO底层原理有一定认识. KVO只要是通过监听set方法,从而实现对该对象的监听. 要监听set方法,有两种实现方式,第一就是使用分类,重写set方法,但是这样就会覆盖父类的set方法,所以不可行,pass掉. 第二就是使用子类,把父类的isa指针改为子类.然后调用父类色set方法,最后调用回调方法,该方案可行. 首先是注册监听,在调用监听方法的时候,会动态实现子类,把observer保存到子类的属性中(弱引用weak类型,不能使用strong,会造成循环引用),并且把类

HTTPS 原理剖析与项目场景

最近手头有两个项目,XX导航和XX产业平台,都需要使用HTTPS协议,因此,这次对HTTPS协议做一次整理与分享. 为什么使用HTTPS HTTP 协议,本身是明文传输的,没有经过任何安全处理.那么这个时候就很容易在传输过程中被中间者窃听.篡改.冒充等风险.这里提到的中间者主要指一些网络节点,是用户数据在浏览器和服务器中间传输必须要经过的节点,比如 WIFI 热点,路由器,防火墙,反向代理,缓存服务器等. HTTP 协议,中间者可以窃听隐私,使用户的敏感数据暴露无遗:篡改网页,例如往页面插的广告

MapReduce/Hbase进阶提升(原理剖析、实战演练)

什么是MapReduce? MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算.概念"Map(映射)"和"Reduce(归约)",和他们的主要思想,都是从函数式编程语言里借来的,还有从矢量编程语言里借来的特性.他极大地方便了编程人员在不会分布式并行编程的情况下,将自己的程序运行在分布式系统上. 当前的软件实现是指定一个Map(映射)函数,用来把一组键值对映射成一组新的键值对,指定并发的Reduce(归约)函数,用来保证所有映射的键值对中的每一

EventBus的使用和原理剖析

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38174537 代码下载:http://download.csdn.net/detail/yuanzeyao2008/7684041 在编程过程中,当我们想通知其他组件某些事情发生时,我们通常使用观察者模式,正式因为观察者模式非常常见,所以在jdk1.5中已经帮助我们实现了观察者模式,我们只需要简单的继承一些类就可以快速使用观察者模式,在Android中也有一个类似功能的开源库EventBu

清除浮动的原理剖析

常用的清除浮动的几种方法总结下: 1,手动设置一个标签(在浮动元素下方),然后对其设置clear属性 2,给浮动元素设置 :after伪类,创建块元素,设置clear属性 3,给父元素设置浮动 4,给父元素设置overflow设置非visible值(auto,hidden) 5,给父元素的display设置为table-cell 7,在ie6,7中,设置zoom或者width,height来触发haslayout,使父元素包含浮动元素 原理剖析: 1,2方法之所以可以成功,是因为了clear属性

Koa框架实践与中间件原理剖析

Koa框架实践与中间件原理剖析 最近尝试用了一下Koa,并在此记录一下使用心得. 注意:本文是以读者已经了解Generator和Promise为前提在写的,因为单单Generator和Promise都能够写一篇博文来讲解介绍了,所以就不在这里赘述.网上资料很多,可以自行查阅. Koa是Express原班人马打造的一个更小,基于nodejs平台的下一代web开发框架.Koa的精妙之处就在于其使用generator和promise,实现了一种更为有趣的中间件系统,Koa的中间件是一系列generat

图像处理之基础---二维卷积运算原理剖析

卷积运算(Convolution)是通过两个函数f 和g 生成第三个函数的一种数学算子,表示函数f 与经过翻转和平移与g 的重叠部分的累积.如果将参加卷积的一个函数看作区间的指示函数,卷积还可以被看作是“滑动平均”的推广.假设: f(x),g(x)是R1上的两个可积函数,并且积分是存在的.这样,随着 x 的不同取值,这个积分就定义了一个新函数h(x),称为函数f 与g 的卷积,记为h(x)=(f*g)(x). 两个向量卷积,说白了就是多项式乘法.下面用个矩阵例子说明其工作原理: a和d的卷积就是

ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.ConfigureServices 3.Configure方法

简单代码生成器原理剖析(二)

上篇<简单代码生成器原理剖析(一)>分 析了代码生成器的原理,查询数据库系统视 图:INFORMATION_SCHEMA.TABLES. INFORMATION_SCHEMA.COLUMNS  可以获得数据库中表.列的相关信息,再运用StringBuilder类的其AppendLine方法追加字符串,最后早运用 File.WriteAllText方法将字符串写入文件. 第二版代码生成器在第一版的基础上扩展了以下功能: 使用了部分类(partial):当使用大项目或自动生成的代码(如由 Wind