iOS Category 添加属性实现原理 - 关联对象

RunTime为Category动态关联对象

  • 使用RunTime给系统的类添加属性,首先需要了解对象与属性的关系。对象一开始初始化的时候其属性为nil,给属性赋值其实就是让属性指向一块存储内容的内存,使这个对象的属性跟这块内存产生一种关联。
  • 那么如果想动态的添加属性,其实就是动态的产生某种关联就好了。而想要给系统的类添加属性,只能通过分类。

1.使用举例

  1. 这里给NSObject添加height属性,创建NSObject的分类

    // 我们可以使用@property给分类添加属性
    @property(nonatomic,assign)int height;
  2. 虽然在分类中可以写@property添加属性,但是不会自动生成私有属性,也不会生成set,get方法的实现,只会生成set,get的声明,需要我们自己去实现。

方法一:我们可以通过使用静态全局变量给分类添加属性

```
static int height_;
-(void)setHeight:(int)height
{height_ = height;
}
- (int)height
{return height_;
}
```
  • 缺点1:这个height_静态全局变量与类并没有关联,无论对象创建与销毁,只要程序在运行height_变量就存在,并不是真正意义上的属性。
  • 缺点2:改变第二个对象会影响到第一个对象的属性的值
    NSObject *obj1 = [[NSObject alloc] init]; obj1.height = 10; NSLog(@"obj1:%i",obj1.height); // 打印结果10 NSObject *obj2 = [[NSObject alloc] init]; obj2.height = 120; NSLog(@"obj1:%i",obj1.height); // 打印结果120 NSLog(@"obj2:%i",obj2.height); // 打印结果120

方法二:使用字典保存对象对应的value

NSMutableDictionary *dictionary_;
+ (void)load
{dictionary_ = [NSMutableDictionary dictionary];
}
-(void)setHeight:(int)height
{// 对象的指针地址作为Key,保证唯一NSString *key = [NSString stringWithFormat:@"%p",self];dictionary_[key] = @(height);
}
- (int)height
{NSString *key = [NSString stringWithFormat:@"%p",self];return [dictionary_[key] intValue];
}
  • 缺点1:这个dictionary_静态全局变量与类并没有关联,无论对象创建与销毁,只要程序在运行dictionary_变量就存在,并不是真正意义上的属性。
  • 缺点2:全局对象会造成内存泄露问题。
  • 缺点3:线程安全问题,同一时间访问。

方法三:使用RunTime关联对象动态添加属性

  • RunTime提供了动态添加属性和获得属性的方法

     static const char Myheight;
    -(void)setHeight:(int)height
    {objc_setAssociatedObject(self, &Myheight, @(height), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (int)height
    {return [objc_getAssociatedObject(self, &Myheight) intValue];
    }

方法四:使用RunTime关联对象动态添加属性,key 值改进

```
-(void)setHeight:(int)height
{objc_setAssociatedObject(self, @selector(height), @(height), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)height
{   _cmd = @selector(height);return [objc_getAssociatedObject(self, @selector(height)) intValue];
}
```

RunTime关联对象方法介绍

  1. 动态添加属性方法:objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

    • 参数一:id object : 给哪个对象添加属性,这里要给自己添加属性,用self。
    • 参数二:void *key == id key : 属性名,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过此key获得属性的值并返回。key值只要是一个指针即可,我们可以传入@selector(name)
    • 参数三:id value : 关联的值,也就是set方法传入的值给属性去保存。
    • 参数四:objc_AssociationPolicy policy : 策略,属性以什么形式保存。
    • 有以下几种
    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {OBJC_ASSOCIATION_ASSIGN = 0,  // 指定一个弱引用相关联的对象OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相关的对象被复制,非原子性OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相关对象的强引用,原子性OBJC_ASSOCIATION_COPY = 01403     // 指定相关的对象被复制,原子性
    };
  2. 获得属性方法:objc_getAssociatedObject(id object, const void *key);
    • 参数一:id object : 获取哪个对象里面的关联的属性。
    • 参数二:void * == id key : 什么属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value。
  3. 移除所有关联对象方法: objc_removeAssociatedObjects
    - (void)removeAssociatedObjects { // 移除所有关联对象 objc_removeAssociatedObjects(self); }

可以看出关联对象的使用非常简单,接下来我们来探寻关联对象的底层原理

2.关联对象原理

1. 实现关联对象技术的核心对象有
  • 其中Map同我们平时使用的字典类似。通过key-value一一对应存值。

    • AssociationsManager
    • AssociationsHashMap
    • ObjectAssociationMap
    • ObjcAssociation
2.对关联对象技术的核心对象有了一个大概的意识,我们通过源码来探寻这些对象的存在形式以及其作用
  1. runtime源码,objc_setAssociatedObject函数在objc-runtime.mm 文件中,首先找到objc_setAssociatedObject函数,其实现
    void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, (void *)key, value, policy); }
  2. 其实内部调用的是_object_set_associative_reference函数,_object_set_associative_reference函数

    void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {// retain the new value (if any) outside the lock.ObjcAssociation old_association(0, nil);id new_value = value ? acquireValue(value, policy) : nil;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());disguised_ptr_t disguised_object = DISGUISE(object);if (new_value) {// break any existing association.AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {// secondary table existsObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;j->second = ObjcAssociation(policy, new_value);} else {(*refs)[key] = ObjcAssociation(policy, new_value);}} else {// create the new association (first time).ObjectAssociationMap *refs = new ObjectAssociationMap;associations[disguised_object] = refs;(*refs)[key] = ObjcAssociation(policy, new_value);object->setHasAssociatedObjects();}} else {// setting the association to nil breaks the association.AssociationsHashMap::iterator i = associations.find(disguised_object);if (i !=  associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;refs->erase(j);}}}}// release the old value (outside of the lock).if (old_association.hasValue()) ReleaseValue()(old_association);
    }
  3. _object_set_associative_reference函数内部我们可以全部找到我们上面说过的实现关联对象技术的核心对象。接下来我们来一个一个看其内部实现原理探寻他们之间的关系。

  4. AssociationsManager, 通过AssociationsManager内部源码发现,AssociationsManager内部有一个AssociationsHashMap对象。
    ```
    class AssociationsManager {
    // associative references: object pointer -> PtrPtrHashMap.
    static AssociationsHashMap *_map;
    public:
    AssociationsManager() { AssociationsManagerLock.lock(); }
    ~AssociationsManager() { AssociationsManagerLock.unlock(); }

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

    };
    ```

  5. AssociationsHashMap,AssociationsHashMap内部的源码。

    #if TARGET_OS_WIN32typedef hash_map<void *, ObjcAssociation> ObjectAssociationMap;typedef hash_map<disguised_ptr_t, ObjectAssociationMap *> AssociationsHashMap;
    #elsetypedef ObjcAllocator<std::pair<void * const, ObjcAssociation> > ObjectAssociationMapAllocator;class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {public:void *operator new(size_t n) { return ::malloc(n); }void operator delete(void *ptr) { ::free(ptr); }};typedef ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap*> > AssociationsHashMapAllocator;class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {public:void *operator new(size_t n) { return ::malloc(n); }void operator delete(void *ptr) { ::free(ptr); }};
    #endif
    }
  6. 通过AssociationsHashMap内部源码我们发现AssociationsHashMap继承自unordered_map首先来看一下unordered_map内的源码

    template <class _Key, class _Tp, class _Hash = hash<_Key>, class _Pred = equal_to<_Key>,class _Alloc = allocator<pair<const _Key, _Tp> > >
    class _LIBCPP_TEMPLATE_VIS unordered_map
    {
    public:// typestypedef _Key                                           key_type;typedef _Tp                                            mapped_type;typedef _Hash                                          hasher;typedef _Pred                                          key_equal;typedef _Alloc                                         allocator_type;typedef pair<const key_type, mapped_type>              value_type;typedef pair<key_type, mapped_type>                    __nc_value_type;typedef value_type&                                    reference;typedef const value_type&                              const_reference;static_assert((is_same<value_type, typename allocator_type::value_type>::value),"Invalid allocator::value_type");private:
  7. 从unordered_map源码中我们可以看出_Key和_Tp也就是前两个参数对应着map中的Key和Value,那么对照上面AssociationsHashMap内源码发现_Key中传入的是disguised_ptr_t,_Tp中传入的值则为ObjectAssociationMap*。
    紧接着我们来到ObjectAssociationMap中,我们发现ObjectAssociationMap中同样以key、Value的方式存储着ObjcAssociation。
    接着我们来到ObjcAssociation中

    class ObjcAssociation {uintptr_t _policy;id _value;public:ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}ObjcAssociation() : _policy(0), _value(nil) {}uintptr_t policy() const { return _policy; }id value() const { return _value; }bool hasValue() { return _value != nil; }};
  8. 我们发现ObjcAssociation存储着_policy、_value,而这两个值我们可以发现正是我们调用objc_setAssociatedObject函数传入的值,也就是说我们在调用objc_setAssociatedObject函数中传入的value和policy这两个值最终是存储在ObjcAssociation中的。
  9. 现在我们已经对AssociationsManager、 AssociationsHashMap、 ObjectAssociationMap、ObjcAssociation四个对象之间的关系有了简单的认识,那么接下来我们来细读源码,看一下objc_setAssociatedObject函数中传入的四个参数分别放在哪个对象中充当什么作用。
    重新回到_object_set_associative_reference函数实现中
  10. 细读上述_object_set_associative_reference源码我们可以发现,首先根据我们传入的value经过acquireValue函数处理获取new_value。acquireValue函数内部其实是通过对策略的判断返回不同的值

    static id acquireValue(id value, uintptr_t policy) {switch (policy & 0xFF) {case OBJC_ASSOCIATION_SETTER_RETAIN:return objc_retain(value);case OBJC_ASSOCIATION_SETTER_COPY:return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);}return value;
    }
  11. 之后创建AssociationsManager manager;以及拿到manager内部的AssociationsHashMap即associations。
  12. 再之后我们看到了我们传入的第一个参数object
    object经过DISGUISE函数被转化为了disguised_ptr_t类型的disguised_object。

      typedef uintptr_t disguised_ptr_t;inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
  13. DISGUISE函数其实仅仅对object做了位运算,之后我们看到被处理成new_value的value,同policy被存入了ObjcAssociation中。而ObjcAssociation对应我们传入的key被存入了ObjectAssociationMap中。disguised_object和ObjectAssociationMap则以key-value的形式对应存储在associations中也就是AssociationsHashMap中。

     // create the new association (first time).ObjectAssociationMap *refs = new ObjectAssociationMap;associations[disguised_object] = refs;(*refs)[key] = ObjcAssociation(policy, new_value);object->setHasAssociatedObjects();
  14. 如果我们value设置为nil的话那么会执行下面的代码

    // setting the association to nil breaks the association.AssociationsHashMap::iterator i = associations.find(disguised_object);if (i !=  associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;refs->erase(j);}}
  15. 从上述代码中可以看出,如果我们设置value为nil时,就会将关联对象从ObjectAssociationMap中移除。

  16. 最后我们通过一张图可以很清晰的理清楚其中的关系

  17. 通过上图我们可以总结为:一个实例对象就对应一个ObjectAssociationMap,而ObjectAssociationMap中存储着多个此实例对象的关联对象的key以及ObjcAssociation,为ObjcAssociation中存储着关联对象的value和policy策略。
  18. 由此我们可以知道关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的map用来存放每一个对象及其对应关联属性表格。

3. objc_getAssociatedObject函数
  1. objc_getAssociatedObject内部调用的是_object_get_associative_reference

    id objc_getAssociatedObject(id object, const void *key) {return _object_get_associative_reference(object, (void *)key);
    }
  2. _object_get_associative_reference函数

    id _object_get_associative_reference(id object, void *key) {id value = nil;uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());disguised_ptr_t disguised_object = DISGUISE(object);AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {ObjcAssociation &entry = j->second;value = entry.value();policy = entry.policy();if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {objc_retain(value);}}}}if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {objc_autorelease(value);}return value;
    }
  3. 从_object_get_associative_reference函数内部可以看出,向set方法中那样,反向将value一层一层取出最后return出去。

4. objc_removeAssociatedObjects函数
  • objc_removeAssociatedObjects用来删除所有的关联对象,
  1. objc_removeAssociatedObjects函数内部调用的是_object_remove_assocations函数

    void objc_removeAssociatedObjects(id object)
    {if (object && object->hasAssociatedObjects()) {_object_remove_assocations(object);}
    }
  2. _object_remove_assocations函数

    void _object_remove_assocations(id object) {vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());if (associations.size() == 0) return;disguised_ptr_t disguised_object = DISGUISE(object);AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {// copy all of the associations that need to be removed.ObjectAssociationMap *refs = i->second;for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {elements.push_back(j->second);}// remove the secondary table.delete refs;associations.erase(i);}}// the calls to releaseValue() happen outside of the lock.for_each(elements.begin(), elements.end(), ReleaseValue());
    }
  3. 上述源码可以看出_object_remove_assocations函数将object对象向对应的所有关联对象全部删除

总结

  1. 关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationsManager中,如果设置关联对象为nil,就相当于是移除关联对象。
    此时我们我们在回过头来看objc_AssociationPolicy policy 参数: 属性以什么形式保存的策略。

    /*** Policies related to associative references.* These are options to objc_setAssociatedObject()*/
    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. *   The association is not made atomically. */OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. *   The association is not made atomically. */OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.*   The association is made atomically. */OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.*   The association is made atomically. */
    };
    • 我们会发现其中只有RETAIN和COPY而为什么没有weak呢?
    • 总过上面对源码的分析我们知道,object经过DISGUISE函数被转化为了 disguised_ptr_t类型的disguised_object。

    • disguised_ptr_t disguised_object = DISGUISE(object);
    • 而同时我们知道,weak修饰的属性,当没有拥有对象之后就会被销毁,并且指针置位nil,那么在对象销毁之后,虽然在map中既然存在值object对应的AssociationsHashMap,但是因为object地址已经被置位nil,会造成坏地址访问而无法根据object对象的地址转化为disguised_object了。

  2. Category能否添加成员变量?如果可以,如何给Category添加成员变量?
  • 不能直接添加成员变量,但是可以通过runtime的方式间接实现添加成员变量的效果。

引用

转载于:https://www.cnblogs.com/ShaoYinling/p/10241407.html

iOS Category 添加属性实现原理 - 关联对象相关推荐

  1. Runtime 系列 3-- 给 category 添加属性

    Associated Objects的作用 Associated Objects的作用一般有如下三种: 为系统类添加私有变量以帮助实现细节: 为系统类添加公有属性: 为 KVO 创建一个关联的观察者. ...

  2. 【iOS】——分类、扩展和关联对象

    目录 一.分类Category和扩展Extension 1.分类Category(运行期) 2.扩展Extension(编译期) 3.分类和扩展的区别 二.分类Category的实质 1.分类的结构 ...

  3. 【iOS】—— 分类、扩展和关联对象

    分类.扩展和关联对象 一.分类与扩展的区别 1.`category`类别(分类) 2.`extension`(类扩展) 二.关联对象的实现 1.关联对象的实现步骤: 2.关联对象分析: 三.关联对象- ...

  4. 【iOS】—— 分类,扩展和关联对象

    分类,扩展和关联对象 文章目录 分类,扩展和关联对象 分类和扩展 分类概念 扩展概念 两者区别 分类的实质 关联对象 通过关联对象给分类添加属性 关联对象的用处 关联对象的API 给声明的属性添加se ...

  5. 如何给iOS 分类添加 属性

    2019独角兽企业重金招聘Python工程师标准>>> 比如我们封装了一个类,不想再动它了,但是我们又需要在那个类中增加一些方法,这时候我们就不必在那个类中做修改或者再定义一个它的子 ...

  6. iOS 分类添加属性

    1.首先在分类文件中导入头文件 #import <objc/runtime.h> 2.实现代码如下: (1).h定义文件 #import <UIKit/UIKit.h>@int ...

  7. javaScript 对象添加属性和创建js对象的方式(以及理解:“无法给构造函数添加新的属性“)

    1.javaScript 对象想要添加属性,非常简单 (1)直接添加,使用语法:objectName.propertyName 添加属性. 举例: var person = new Object(); ...

  8. 【iOS开发】——Category底层原理、Extension、关联对象

    [iOS开发]--Category底层原理.Extension.关联对象 Category是什么?它可以用来干什么? Category特点 Category的实质以及实现过程 Category结构体 ...

  9. [iOS]-Category、Extension和关联对象

    目录: 参考的博客: 前言 一.Category分类 Extension扩展 Category的实质 Category结构体 将分类转成C++看起 对象方法列表结构体 类方法列表结构体 协议列表结构体 ...

最新文章

  1. 题目梳理(一)(2019.07.06~2019.07.20)
  2. Navicat Premium 12.0.24破解
  3. Android libcutils库中整数溢出导致的堆破坏漏洞的发现与利用
  4. Web前端开发应该避免的几个思维误区
  5. MySql优化 (2009-08-28 系统架构师大会) 演讲ppt
  6. [Linux]gdb调试多进程多线程例程
  7. (pytorch-深度学习)SE-ResNet的pytorch实现
  8. 程序员,35岁就可能被替换的职业,遇到好的领导多么重要
  9. 重构图书馆惊魂夜(理解模型,关注设计)
  10. “OSPF” 开销值、协议优先级及计时器的修改
  11. monkey4444勒索病毒解密方法方案成功处理复旦安全实验室
  12. 拓端tecdat|R语言用逻辑回归、决策树和随机森林对信贷数据集进行分类预测
  13. 微信小程序 访问locolhost_微信小程序开发——本地调试
  14. 小网站云服务器配置推荐,小网站云服务器配置推荐
  15. PDM,读《长尾理论》
  16. java项目中数据查询慢问题
  17. 二叉树的前序遍历-python
  18. 【产业互联网周报】硅谷多家IT大厂组成现代计算联盟,提供企业云上服务;台积电新封装技术2023年投产...
  19. SwiftUI 界面大全之宠物领养管理App(教程含源码)
  20. 用层次分析法建模解决交通问题论文

热门文章

  1. 数据仓库面试题——数据仓库分层
  2. 安全网站域名是什么样的?有什么作用?
  3. vue 使用a标签跨域下载文件
  4. 高通QCC30xx_QCC51xx_如何 DFU升级 OTA升级
  5. 【HCIA 03】华为静态路由配置
  6. Python毕业设计 机器视觉图像拼接算法研究与实现 - opencv
  7. 最新宝塔去除强制绑定手机号
  8. MySQL优化器如何预估查询成本
  9. 解析PPTX 遇到异常:“\b”(十六进制值 0x08)是无效的字符。
  10. 算法训练 子网掩码(25分)c++实现