iOS 底层学习17

前言

iOS 底层第17天的学习。今天主要分享的就是对 category 一些相关知识点的补充


类扩展分析

  • 分析前准备👇
// 类扩展写在申明之前,实现之后
@interface XKStudent ()

@property (nonatomic, copy) NSString *ext_hobby;
@property (nonatomic, assign) int  ext_age;

- (void) ext_instanceMethod;
+ (void) ext_classMethod;

@end

@implementation XKStudent

- (void) ext_instanceMethod {
}
+ (void) ext_classMethod {
}
  • 准备完毕后,clang 一下 XKStudent.m 编译成 .cpp 文件
clang -rewrite-objc XKStudent.m -o XKStudent.cpp
  • 打开 XKStudent.cpp 文件,搜索 ext_instanceMethod
  • 👆 得知 类的扩展 的实例方法会在编译 时存在于 method_list_t,但我们还不能确定真的如编译器显示的那样,会直接加载到 method_list_t
  • 接下来我们继续验证 , 调用 load 进行非懒加载, 验证 类扩展 是否和 category 一样
@implementation XKStudent
+ (void) load {} 
- (void) ext_instanceMethod {
}
+ (void) ext_classMethod {
}
- (void)teacherSay{}
@end
  • 进入 realizeClassWithoutSwift 输入出 ro 👇
小结
  • 可知在进行非懒加载类扩展 里的实例方法 会在编译时存在于 method_list,这一点是与 category 不同的。
  • extension 类扩展
    • 特殊的分类
    • 可以给类添加成员变量,但都是私有的
    • 可以给类添加方法,但都是私有的
  • category 类别,分类
    • 专门用来给类添加新的方法
    • 不能用常规的方法添加成员属性,成员变量,也无法取到,但可以通过 runtime 给分类添加属性
    • 分类中用 @property 定义变量,只会生成 getter,setter 方法的声明,不能生成方法实现和带下划线的成员变量

category 关联对象分析

  • 当我们不能用常规的方法给category 添加成员属性,成员变量,也无法取到,但可以通过 runtime api 给分类添加属性

而这个 api - objc_setAssociatedObject ,那 objc_setAssociatedObject 是如何给 category 添加成员属性的?

objc_setAssociatedObject

  • 分析前准备👇
#import "XKStudent+XKA.h"
#import "objc/runtime.h"

@implementation XKStudent (XKA)

- (void)setCname:(NSString *)cname {
    objc_setAssociatedObject(self, "c_name", cname,OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *) cname {
    return objc_getAssociatedObject(self,"c_name");
}
  • 进入 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
// 核心方法
// id object:  对象 eg:XKStudent
// const void *key: 键值 
// id value: eg: 存储的数据 eg:小 name = 小看emiya
// uintptr_t policy: 缓存测量 retain, copy
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;
    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
    // 包装成同一的数据结构 disguised = 伪装;假装
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // 对象的存储
    ObjcAssociation association{policy, value};
    // retain the new value (if any) outside the lock.
    association.acquireValue();
    bool isFirstAssociation = false;
    {
         // ... 核心逻辑 
    }
   // Call setHasAssociatedObjects outside the lock, since this
    // will call the object's _noteAssociatedObjects method if it
    // has one, and this may trigger +initialize which might do
    // arbitrary stuff, including setting more associated objects.
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).
    association.releaseHeldValue();
}

  • 先静态全局分析一下
    - DisguisedPtr<objc_object> disguised{(objc_object *)object} 包装伪装成同一的数据结构
    - ObjcAssociation association{policy, value} 对象的存储 ,policy = 缓存策略 ,value = 值
    - bool isFirstAssociation = false; { ... } 开始插入表操作

  • 开始动态分析

  • 输出👇
  • 可知 association 的确存储了valuepolicy, disguised 包装了 object
  • 继续往下 step
  • 进入 ·AssociationsManager
  • mapStorage 静态变量,可知 ObjectAssociationMap 是唯一的表
  • 继续 step
  • 输出一下 associations
  • 当进行了 associations.try_emplace(disguised, ObjectAssociationMap{}); ,再次输出
    associations
  • 可知 associations 里的 buckets 已经有了地址了。
  • 进入 associations.try_emplace
  • 这时是第一次进入 LookupBucketFor ,没有找到 bucket ,继续往下 step
  • 输出 refs_result
  • 继续 step
  • 输出 refs 👆 得知 refsrefs_result.first->second 读取到的
  • 里面的 Buckets = nil
  • 继续 step 👇
  • 输出 result 查看返回结果
  • 可知再进行 try_emplace(key, std::move(association)) 这步操作时,把 association 存储到了
    ObjcAssociationMap, 之后再把 ObjcAssociationMap 插入到 buckets

  • 继续 step,设置 value = nil 来到 else👇

  • 可知当 value = nil 找到当前 disguised_key 所绑定的 refs_it, 根据 key 找到 it, 最后再 erase .

总结

  • 看一下图,或许能更容易理解 objc_setAssociatedObject 底层实现,图有点丑多多包涵!
  • AssociationsHashMap 是一张全局的总表,一层一层的嵌套,以键值的形式存储数据👇
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容