一探究竟:Objective-C isa指针及KVO实现原理

1、什么是isa指针
概念:

Every object has an isa instance variable that identifies the object's class. The runtime uses this pointer to determine the actual class of the object when it needs to.

每个对象都有一个标识对象类的isa实例变量。运行时使用此指针来确定对象需要时的实际类。
这就好比把isa拆开成 is a(是什么类的实例)的意思。

代码中的isa:

在objc.h文件中有这样定义:


图1

从图中我们可以看出三点:
1、 id类型是一个objc_object结构体的指针。
2、objc_object结构体包含一个Class 类型的变量isa。
3、 Class是objc_class结构体的指针。

事实上在objc的runtime中,类是用 objc_class 结构体表示的,对象是用 objc_object 结构体表示的。这也就解释了为什么id类型可以指向OC中任意对象类型了。

到了这里我们只需要再明白objc_class结构体的内容就可以了。在runtime.h文件中objc_class结构体定义如下:

struct objc_class {
    Class isa  //所属类的指针
    Class super_class//指向父类的指针                                        
    const char *name    //类名                                     
    long version            // 版本                                 
    long info                   //供运行期使用的一些位标识。                             
    long instance_size      //实例大小                                 
    struct objc_ivar_list *ivars       //成员变量数组                      
    struct objc_method_list **methodLists  //方法列表                  
    struct objc_cache *cache//指向最近使用的方法.用于方法调用的优化                            
    struct objc_protocol_list *protocols//协议的数组                     
} 

当看到objc_class结构体的第一个变量也是Class类型的指针时,是不是很崩溃。不必难过,其实这正好验证了万物皆对象的事实。类也是对象,他是meteClass(元类)的实例
到这里我们来整理下思路:

  • 实例对象在运行时被表示成objc_object类型结构体,结构体内部有个isa指针指向objc_class结构体。
  • objc_class内部保存了类的变量和方法列表以及其他一些信息,并且还有一个isa指针。这个isa指针会指向meteClass(元类),元类里保存了这个类的类方法列表。
  • 为了完整性,其实元类里也有一个isa指针,这个isa指针,指向的是根元类,根元类的isa指针指向自己
    大致如下面逻辑:
    实例对象--(runtime)-->objc_object--(isa)-->objc_class--(isa)-->元类--isa-->根元类--isa-->自己。
    然而值得一提的是:当我们调用某个类的方法时,如果这个类的方法列表里没有该方法,则会去找这个类的父类的方法列表。这种机制就是通过objc_class的第二个变量super_class指针实现的。并且这种继承关系会扩展到元类。最终类似于下图的一种关系:
关系图
2、KVO的实现原理

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.

从Apple文档中我们大概可以了解到:
KVO是通过"isa-swizzling"技术来实现的,当一个对象注册观察者时,这个对象的isa指针被修改指向一个中间类。
KVO 的实现依赖于 Objective-C 强大的 runtime(这里不详细讲解,我准备开一篇结合实例讲解下runtime,喜欢我文章的可以关注下O(∩_∩)O~~)。当观察A类型的对象时,在运行时会创建了一个集成自A类的NSKVONotifying_A类,且为NSKVONotifying_A重写观察属性的setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察者属性值的更改情况
假设A类有个name属性,NSKVONotifying_A重写setName方法:

- (void) setName:(NSString *)name
{
    [self willChangeValueForKey:@"name"];
    [super setName:name];
    [self didChangeValueForKey:@"name"];
}

被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变更,来保存旧值;当改变发生后,didChangeValueForKey:被调用,通知系统该 keyPath 的属性值已经变更;之后,observeValueForKey:ofObject:change:context:就会被调用。

3、手动触发KVO

以上我们说的都是自动触发,只要我们注册了观察者。只要被观察的属性值一改变就会调用observeValueForKey:ofObject:change:context:方法,而有时候我们在特定的情况下,才去通知观察者被观察的属性改变了。这就需要我们手动触发KVO。大致步骤:
1、取消自动触发:重写+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
2、重写属性的setter方法,根据需求判断是否需要调用willChangeValueForKey:didChangeValueForKey:方法。
代码如下:

- (void)setName:(NSString *)name{
    
    if ([name isEqualToString:@"小白"]) {
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }else{
         _name = name;
    }
   
}

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
    // 这里只是取消了name属性的自动触发
    if ([key isEqualToString:@"name"]) {
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}

这样写之后,只有当Dog类对象的name属性等于"小白"时才会通知观察者属性改变了。
注意:取消自动触发的方法如果直接返回NO,那么这个类的对象的所有属性值都会取消自动触发。所以最好根据需要自己判断。


从上面的介绍中我们看到KVO的调用其实很繁琐,如果有个带有block的方法就好了,这篇文章手把手教你封装一个带有block的KVO方法。
-------------完--------------
最后:喜欢我文章的可以多多点赞和关注,您的鼓励是我写作的动力。O(∩_∩)O~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容