10-iOS分类关联对象底层实现原理

简介: 10-iOS分类关联对象底层实现原理

分类添加属性

回顾之前分类的结构:

struct _category_t {
  const char *name;
  struct _class_t *cls;
  const struct _method_list_t *instance_methods;
  const struct _method_list_t *class_methods;
  const struct _protocol_list_t *protocols;
  const struct _prop_list_t *properties;
};

从结构可知,里面存储着属性列表,可以给分类添加属性:

创建Person类,并创建分类,然后添加属性:

@interface Person (Test)
@property(strong, nonatomic) NSString *name;
@end
@implementation Person (Test)
@end
调用:
Person *person = [Person new];
person.name = @"zhangsan";

运行结果:

-[Person setName:]: unrecognized selector sent to instance 0x1005b1c20
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person setName:]: unrecognized selector sent to instance 0x1005b1c20'
*** First throw call stack:
(
  0   CoreFoundation 0x00007fff204fb6af __exceptionPreprocess + 242
  1   libobjc.A.dylib 0x00007fff202333c9 objc_exception_throw + 48
  2   CoreFoundation 0x00007fff2057dc85 -[NSObject(NSObject) __retain_OA] + 0
  3   CoreFoundation 0x00007fff2046307d ___forwarding___ + 1467
  4   CoreFoundation 0x00007fff20462a38 _CF_forwarding_prep_0 + 120
  5   OCTest 0x0000000100003f10 main + 80
  6   libdyld.dylib 0x00007fff203a4621 start + 1
  7   ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person setName:]: unrecognized selector sent to instance 0x1005b1c20'
terminating with uncaught exception of type NSException

发现name默认并没有实现setter方法,getter方法也未实现;

分类如何添加属性?
可以通过以下几种方式: 1. 可以使用全局变量,自己实现getter和setter,利用给全局变量的赋值方式实现
2. 可以使用字典,在load方法里面进行初始化字典,通过setter和getter对字典进行设置值和取值(注意线程安全问题)
3. 关联对象

关联对象相关函数以及参数说明

  • 导入头文件

#import <objc/runtime.h>

  • 设置值
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
  • Object:当前对象value:要设置的值;
    key:可以使用以下几种方式:
1
static void *Key = &Key;
2 static char Key;
3
属性名作为key;
4
getter的方法名 @selector 作为key(_cmd);
  • objc_AssociationPolicy对应关系:
• typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
•     OBJC_ASSOCIATION_ASSIGN = 0, //assign
•     OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //strong, nonatomic
•     OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //copy, nonatomic
•     OBJC_ASSOCIATION_RETAIN = 01401, //strong, atomic
•     OBJC_ASSOCIATION_COPY = 01403 //copy, atomic
• };

取值

objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull
  • Object:当前对象
    key:同上

关联对象的使用

测试代码:

@implementation Person (Test)
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
    return objc_getAssociatedObject(self, _cmd);
}
@end

调用以及运行结果:

Person *person = [Person new];
person.name = @"zhangsan";
NSLog(@"%@", person.name); //打印结果zhangsan

关联对象的实现原理?

源码分析objc_setAssociatedObject源码实现:

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}

接下来调用_object_set_associative_reference:

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return;
    //将object封装成为了DisguisedPtr
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    //将policy以及value封装成为了ObjcAssociation
    ObjcAssociation association{policy, value};
    // 根据policy确定策略对value的处理是retain还是copy
    association.acquireValue();
    bool isFirstAssociation = false;
    //局部作用域
    {
        //初始化AssociationsManager
        AssociationsManager manager;
        //获取全局的AssociationsHashMap
        AssociationsHashMap &associations(manager.get());
        //如果value有值
        if (value) {
            //try_emplace函数,disguised(将Object传入后封装的结果)作为key查找
            //如果在associations map中,就把TheBucket作为参数对DenseMapIterator进行初始化,接着调用make_pair;
            //key没在associations map中就把disguised作为key,ObjectAssociationMap 作为value存入TheBucket中
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {//判断第二个存不存在
                /* it's the first association we make */
                isFirstAssociation = true;
            }
            /* establish or replace the association */
            auto &refs = refs_result.first->second;
            //查找当前的key是否有association关联对象
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {//如果结果不存在交换policy和value
                association.swap(result.first->second);
            }
        } else {//value为空
            //利用disguised查找ObjectAssociationMap,返回一个迭代器
            auto refs_it = associations.find(disguised);
            //如果找到了
            if (refs_it != associations.end()) {
                //拿到ObjectAssociationMap
                auto &refs = refs_it->second;
                //ObjectAssociationMap表中查找key对应的association
                auto it = refs.find(key);
                //如果找到了
                if (it != refs.end()) {
                    //交换
                    association.swap(it->second);
                    //从表中移除关联
                    refs.erase(it);
                    if (refs.size() == 0) {//没有关联的值了,从associations表中擦除ObjectAssociationMap表
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }
    if (isFirstAssociation)
        object->setHasAssociatedObjects(); //isa对应的符号位设置为存在关联对象
    // release the old value (outside of the lock).
    association.releaseHeldValue();
}

根据代码可知(可参考注释),调用关系总结如下:1. AssociationsManager中有

AssociationsHashMap
class AssociationsManager {
    AssociationsHashMap &get() {
        return _mapStorage.get();
    }
};

2. AssociationsHashMap中将当前类的对象object封装成的DisguisedPtr作为

AssociationsHashMap的key,ObjectAssociationMap作为value,进行存储:
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

3. ObjectAssociationMap中将我们传入的key作为key,ObjcAssociation作为value进行存储:

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;

4. ObjcAssociation中存储着对应的策略和值:

class ObjcAssociation {
    uintptr_t _policy;
    id _value;
};

调用示意图:

总结

能否给分类添加属性或者成员变量?

  • 可以添加属性,由分类的结构可知,分类中存储的有属性列表,但是不能够为分类添加成员变量,因为不存在成员变量列表;
  • 添加属性的时候,当声明属性之后,系统并未为分类的属性实现setter和getter方法,故需要自己手动实现属性的getter和setter,由于不存在_开头的成员变量,需自己维护设置值和取值对应的操作,可以通过全局变量、全局字典(考虑线程安全问题)、关联对象等方式进行添加;

关联对象相关函数?

  • objc_setAssociatedObject(设置值):传入key和策略(根据属性的类型选择对应的策略)等参数
  • objc_getAssociatedObject(取值):传入Object和key

关联对象实现原理?

  • 关联对象并不是存储在被关联对象本身内存中;
  • 关联对象存储在全局的统一的一个AssociationsManager管理的HashMap中;
  • 如果设置关联对象为nil,就相当于是移除关联对象;

本文部分内容来可能来源于网络,发布的内容如果侵犯了您的权益,请联系我们尽快删除!

相关文章
|
5月前
|
iOS开发 网络架构 UED
ios app的分类与本质,感想
ios app的分类与本质,感想
57 0
|
人工智能 文字识别 API
iOS MachineLearning 系列(4)—— 静态图像分析之物体识别与分类
本系列的前几篇文件,详细了介绍了Vision框架中关于静态图片区域识别的内容。本篇文章,我们将着重介绍静态图片中物体的识别与分类。物体识别和分类也是Machine Learning领域重要的应用。通过大量的图片数据进行训练后,模型可以轻易的分析出图片的属性以及图片中物体的属性。
347 0
|
机器学习/深度学习 iOS开发 计算机视觉
iOS MachineLearning 系列(16)—— 几个常用的图片分类CoreML模型
对于图片识别分类的模型来说,其输入和输出都一样,输入都为图像参数,输入为两部分,一部分为最佳预测结果,一部分为可能得预测结果及其可信度。
384 0
|
iOS开发 网络架构 BI
|
iOS开发
iOS - OC Category 分类
1、Category 1)分类/类别(category): 允许以模块的方式向现有类定义添加新的方法(默认不能添加实例变量)。用以扩展自己或他人以前实现的类,使它适合自己的需要。 分类的名称括在类名之后的一对圆括号 "( )" 中。
951 0
|
XML JSON iOS开发
开源 iOS 项目分类索引大全
mattt大神的发布程序:https://github.com/nomad/shenzhen ----------------Mac完整项目----------电台:https://github.com/myoula/sostart----------------iOS完整项目----------------1,豆瓣相册 https://github.com/TonnyTao/Do
5710 0
|
图形学 iOS开发 Windows
iOS UIKit 框架 346 篇文档分类整理 - 预告
iOS UIKit 框架 346 篇文档分类整理 - 预告 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循“署名-非商业用途-保持一致”创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS、Android、Html5、Arduino、pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作。
1229 0
|
XML 网络协议 iOS开发
iOS Foundation 框架 224 篇相关文档分类整理
iOS Foundation 框架 224 篇相关文档分类整理 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循“署名-非商业用途-保持一致”创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS、Android、Html5、Arduino、pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作。
1031 0