美文网首页
428,Objective-C通过关联的对象为类增加属性及原理解

428,Objective-C通过关联的对象为类增加属性及原理解

作者: 枫叶1234 | 来源:发表于2021-01-23 12:24 被阅读0次

动态语言的最大好处,就是灵活性,对于Objective-C来说,能在运行时动态地为类增加方法和实例变量是很多其它语言羡慕不已的能力。现在说说为类增加实例变量用到的技术:联合存储。

一、联合存储的实现方式

下面这段代码实现了为Duck类增加color属性:
Duck+associative.h文件

#import "Duck.h"

@interface Duck (associative)

@property (nonatomic, retain) NSString *color;

@end

Duck+associative.m文件

#import "Duck+associative.h"
#import <objc/runtime.h>

@implementation Duck (associative)

static char colorKey = NULL;

- (NSString *)color {
    return objc_getAssociatedObject(self, &colorKey);
}

- (void)setColor:(NSString *)aColor {
    objc_setAssociatedObject(self, &colorKey,
                             aColor,
                             OBJC_ASSOCIATION_RETAIN);
}

调用举例:

Duck    *smallDuck = [[Duck alloc] init];
smallDuck.color = @"red color";
NSLog(@"duck color:%@",smallDuck.color);
[smallDuck release];

输出结果:

2013-07-18 19:09:26.578 ObjcRunTime[429:403] duck color:red color

至此,我们已经成功的为Duck类增加了一个color属性。

二、为类动态增加属性用到的技术

主要用到了三种设计模式:

1、访问器(accessor)

访问器模式是Cocoa设计模式中很重要的一个,使用它的目的是通过少量的方法(通常时get和set方法)来访问类中每个实例的引用。通过该技术,尽管Duck类没有color实例变量,但是通过联合存储,依然可以实现同访问实例变量完全一样的效果。这些对于类的使用者来说,屏蔽了实现细节。
可以说,访问器模式是通过联合存储实现为类增加属性的必要前提。

2、类别(category)

类别可以在运行时为类动态的增加方法,这是可以利用访问器模式实现为类增加属性的基础。

3、联合存储(associative storage)

通过类别和访问器,再结合联合存储技术,我们完成了为类增加属性的功能。这一切让用户觉得好像真的增加新的实例变量了,但是实际上我们只是通过访问器模拟出来了一个,而不是真正的增加了。

三、联合存储的实现原理

从上面的例子可以看出,为类增加属性看起来是so easy的事情,主要是调了objc_setAssociatedObject和objc_getAssociatedObject两个方法。我的疑问是为类增加的属性对应的对象值存储在哪了呢?下面通过这两个方法的实现部分来寻找答案:

1、objc_setAssociatedObject方法的实现部分:
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) {
    if (UseGC) {
        //这部分是有垃圾回收机制的实现,我们不用管
        if ((policy & OBJC_ASSOCIATION_COPY_NONATOMIC) == OBJC_ASSOCIATION_COPY_NONATOMIC) {
            value = objc_msgSend(value, @selector(copy));
        }
        auto_zone_set_associative_ref(gc_zone, object, key, value);
    } else {
        //这是引用计数机制部分的实现
        // Note, creates a retained reference in non-GC.
        _object_set_associative_reference(object, key, value, policy);
    }
}

从上述方法中可以看出,objc_setAssociatedObject实际上调用的是:

2、_object_set_associative_reference方法的实现部分:
__private_extern__ void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    uintptr_t old_policy = 0;
    // NOTE:  old_policy is always assigned to when old_value is non-nil.
    id new_value = value ? acquireValue(value, policy) : nil, old_value = nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (new_value) {
            //如果new_value不为空,开始遍历associations指向的map,查找object对象是否存在保存联合存储数据的ObjectAssociationMap对象
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(object);
            if (i != associations.end()) {
                //object对象在associations指向的map中存在一个ObjectAssociationMap对象refs
                //检查refs中是否使用key保存过value
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    //使用过该key保存value,用新的value和policy替换掉原来的值
                    ObjcAssociation &old_entry = j->second;
                    old_policy = old_entry.policy;
                    old_value = old_entry.value;
                    old_entry.policy = policy;
                    old_entry.value = new_value;
                } else {
                    //没用使用过该key保存value,将value和policy保存到key映射的map中
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // object对象在associations指向的map中不存在一个ObjectAssociationMap对象
                // 则新建一个ObjectAssociationMap对象,并将new_value通过key的地址映射到该map中保存
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                _class_assertInstancesHaveAssociatedObjects(object->isa);//通知object对象,绑定了一个新的值
            }
        } else {
            //new_value为空,如果使用过该key保存过value,则解除使用该key保存的value值
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(object);
            if (i != associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    ObjcAssociation &old_entry = j->second;
                    old_policy = old_entry.policy;
                    old_value = (id) old_entry.value;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_value) releaseValue(old_value, old_policy);
}

顺便一张图

image.png
3、通过这个方法的实现部分可以清楚的看出,在runtime系统中:

①有一个单例的AssociationsHashMap实例
该实例的生成方式如下:

AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new(::_malloc_internal(sizeof(AssociationsHashMap))) AssociationsHashMap();
        return *_map;
    }

②AssociationsHashMap实例用于保存一个个的ObjectAssociationMap对象
③每个类都拥有一个ObjectAssociationMap实例,每个类通过联合存储模式保存的键值对也都保存在ObjectAssociationMap实例中。
④Key对应的值无所谓,我们需要的是key的地址,因此定义key时通常的写法是:

static char colorKey = NULL;

也就是说,说有的数据其实还是保存在AssociationsHashMap实例中,现在似乎一切都豁然开朗了!

四、联合存储的优缺点

1、优点
联合存储的最大的优点,在于它能通过灵活的方式实现为类增加属性。
2、缺点
效率低,使用单条机器指令就可以访问真正的实例变量,但是访问存储在映射表中的值需要多个函数调用,效率问题还是需要注意的。

事实上,目前许多Cocoa类,像NSAttributedString、NSFileManager、NSNotification、NSProcessInfo等都广泛地使用了联合存储。

相关文章

  • 428,Objective-C通过关联的对象为类增加属性及原理解

    动态语言的最大好处,就是灵活性,对于Objective-C来说,能在运行时动态地为类增加方法和实例变量是很多其它语...

  • OC关联对象小结(一)

    OC关联对象小结(一) 使用场景 为现有的类添加属性,变量 在Objective-C中可以通过Category给一...

  • iOS Objective-C 关联对象

    iOS Objective-C 关联对象 1. 关联对象简介 对于关联对象,我们熟悉它的地方就是给分类添加属性。虽...

  • 动态生成关联对象属性的存取方法

    Objective-C的Category可以灵活的为已经存在的类增加方法,但是不能增加“存储属性”,如果想要扩展类...

  • iOS原理篇:关联对象

    前言 Swift中不能再extension中为类添加存储属性,如何利用关联对象变相添加属性呢? 关联对象相关API...

  • Objective-C--关联对象(AssociateObjec

    关联对象的用途 在Category中为已经注册的类增加存储字段,模拟实例变量。 关联对象存储原理 所有的关联对象都...

  • OC语法 Category底层结构

    用途 为已有类添加额外的实例方法、类方法,属性,协议,或通过runtime 关联对象 间接添加成员变量。分类内添加...

  • Objective-C属性详解

    Objective-C属性详解 理解属性 谈到属性,人们总是说这个对象的属性指向一个什么对象,把那个属性设置为一个...

  • iOS中的runtime理解

    Runtime的使用:1.动态获取/创建类2.动态为一个类增加属性(关联对象)或方法3.在程序运行过程中遍历类中的...

  • 使用 RunTime 给分类添加属性

    通常在分类中只能声明方法,不能添加属性变量,但是其实可以通过关联对象(Objective-C Associated...

网友评论

      本文标题:428,Objective-C通过关联的对象为类增加属性及原理解

      本文链接:https://www.haomeiwen.com/subject/nsayzktx.html