美文网首页
类拓展和关联对象

类拓展和关联对象

作者: spades_K | 来源:发表于2020-12-03 14:43 被阅读0次

1. 类拓展和分类

category 类别/分类:

  • 专门用来给类添加新的方法。
  • 不能给类添加成员属性,添加了成员变量,也无法取到。
  • 可通过 runtime给分类添加属性。
  • 分类中用@proprty定义变量,不生成gettersetter方法和带下划线的成员变量。

extension 类拓展:

  • 可以说是特殊的分类,也称作匿名分类。
  • 可以添加属性和方法,但都是私有的。

举个栗子🌰,在main.m中做如下声明

#import <Foundation/Foundation.h>
/**********************主类声明***************************/
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;

- (void)kc_instanceMethod1;
- (void)kc_instanceMethod2;
- (void)kc_instanceMethod3;

@end
/**********************类拓展***************************/
/// 只能放这里
@interface LGPerson()
@property (nonatomic, copy) NSString *ex_name;
- (void)kc_exinstanceMethod1;

@end
/**********************主类实现***************************/

@implementation LGPerson
- (void)kc_instanceMethod3{
}

- (void)kc_instanceMethod1{
}

- (void)kc_instanceMethod2{
}

- (void)kc_exinstanceMethod1
{
    
}
@end

/**********************分类声明***************************/
@interface LGPerson (CA)
@property (nonatomic, copy) NSString *cate_name;

- (void)kc_cateMethod1;
- (void)kc_cateMethod2;
- (void)kc_cateMethod3;

@end

/**********************分类实现***************************/
@implementation LGPerson (CA)
- (void)kc_cateMethod1
{
    
}
- (void)kc_cateMethod2
{
    
}
- (void)kc_cateMethod3
{
    
}
@end

/**********************main函数***************************/
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [[LGPerson alloc] init];
//        person.cate_name = @"";
        NSLog(@"");
        // Setup code that might create autoreleased objects goes here.
    }
    return 0;
}

通过下面终端命令进行编译,查看main.cpp文件。

clang -rewrite-objc main.m -o main.cpp

LGPerson编译后结构体.png LGPerson编译后方法列表.png

编译后的LGPerson属性包括本类声明中的kc_name和扩展中的ex_name,还有这两个属性的gettersetter方法,也包括分类中的kc_exinstanceMethod1方法。

LGPerson (CA)编译后方法和属性列表.png

LGPerson (CA)分类编译后_prop_list_t生成cate_name属性,与_method_list_t方法列表生成kc_cateMethod1/2/3三个方法,并没有生成cate_namegettersetter方法,也就是没法直接访问cate_name属性,一般分类中都是通过objc_setAssociatedObject()方法关联属性给分类添加属性。
main.m中声明的分类都是非懒加载分类,结合上一篇类、分类的加载文章探索的结果可知:LGPerson类会在此类调用的第一个方法时进行类的初始化,属性方法列表在编译时就放入data()中,在methodizeClass方法下断点来验证下。

(lldb) p list
(method_list_t *) $0 = 0x0000000100008038
(lldb) p *$0
(method_list_t) $1 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 24
    count = 12
    first = {
      name = "kc_cateMethod1"
      types = 0x0000000100003f86 "v16@0:8"
      imp = 0x0000000100003cf0 (KCObjc`-[LGPerson(CA) kc_cateMethod1] at main.m:57)
    }
  }
}
(lldb) p $1.get(0)
(method_t) $2 = {
  name = "kc_cateMethod1"
  types = 0x0000000100003f86 "v16@0:8"
  imp = 0x0000000100003cf0 (KCObjc`-[LGPerson(CA) kc_cateMethod1] at main.m:57)
}
(lldb) p $1.get(1)
(method_t) $3 = {
  name = "kc_cateMethod2"
  types = 0x0000000100003f86 "v16@0:8"
  imp = 0x0000000100003d00 (KCObjc`-[LGPerson(CA) kc_cateMethod2] at main.m:61)
}
(lldb)  p $1.get(2)
(method_t) $15 = {
  name = "kc_instanceMethod2"
  types = 0x0000000100003f86 "v16@0:8"
  imp = 0x0000000100003bb0 (KCObjc`-[LGPerson kc_instanceMethod2] at main.m:35)
}
(lldb)  p $1.get(3)
(method_t) $16 = {
  name = "kc_exinstanceMethod1"
  types = 0x0000000100003f86 "v16@0:8"
  imp = 0x0000000100003bc0 (KCObjc`-[LGPerson kc_exinstanceMethod1] at main.m:39)
}

(lldb) p $1.get(11)
(method_t) $4 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f86 "v16@0:8"
  imp = 0x0000000100003cb0 (KCObjc`-[LGPerson .cxx_destruct] at main.m:28)
}

(lldb) p ro
(const class_ro_t *) $5 = 0x00000001000081e0
(lldb) p $5 ->ivars
(const ivar_list_t *const) $6 = 0x0000000100008228
(lldb) p *$6
(const ivar_list_t) $7 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x0000000100008288
      name = 0x0000000100003ea6 "_kc_name"
      type = 0x0000000100003f7a "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
  offset = 0x0000000100008288
  name = 0x0000000100003ea6 "_kc_name"
  type = 0x0000000100003f7a "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
  offset = 0x0000000100008290
  name = 0x0000000100003eaf "_ex_name"
  type = 0x0000000100003f7a "@\"NSString\""
  alignment_raw = 3
  size = 8
}

(lldb) p proplist
(property_list_t *) $10 = 0x0000000100008160
(lldb) p *$10
(property_list_t) $11 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 3
    first = (name = "cate_name", attributes = "T@\"NSString\",C,N")
  }
}
(lldb) p $11.get(0)
(property_t) $12 = (name = "cate_name", attributes = "T@\"NSString\",C,N")
(lldb) p $11.get(1)
(property_t) $13 = (name = "ex_name", attributes = "T@\"NSString\",C,N,V_ex_name")
(lldb) p $11.get(2)
(property_t) $14 = (name = "kc_name", attributes = "T@\"NSString\",C,N,V_kc_name")

baseMethods()中已经包含kc_exinstanceMethod1类拓展方法,ivars中没有cate_nameproplist中包含。
我们知道类拓展中的方法是不能直接调用的,编译器会报错,那用[person performSelector:@selector(kc_exinstanceMethod1)];底层是objc_msgSend,会能调用到方法吗?方法慢速查找流程中分析过,会从cls->data()->methods()中通过二分查找寻找方法,kc_exinstanceMethod1方法已经在data()->methods()中,来验证下。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [[LGPerson alloc] init];
        [person performSelector:@selector(kc_exinstanceMethod1)];
        NSLog(@"");
    }
    return 0;
}
// LGPerson中实现
- (void)kc_exinstanceMethod1
{
    NSLog(@"%s",__func__);
}

kc_exinstanceMethod1.png

2.关联对象

一般都是通过下面方法实现分类添加属性,这篇文章主要研究下objc_setAssociatedObject是怎么实现关联对象的。

@implementation LGPerson (CA)

- (void)setCate_name:(NSString *)cate_name
{
    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)cate_name
{
    return objc_getAssociatedObject(self, "cate_name");
}
@end

查看源码:

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    SetAssocHook.get()(object, key, value, policy);
}
// SetAssocHook声明
static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};

SetAssocHook其实就是_base_objc_setAssociatedObject,来验证下。

_base_objc_setAssociatedObject验证.png

_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;
    // 是否是禁止关联对象的类
    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));
    // 包装object
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // 包装policy, value
    ObjcAssociation association{policy, value};
    
    // retain the new value (if any) outside the lock. // 对 OBJC_ASSOCIATION_SETTER_RETAIN 和OBJC_ASSOCIATION_SETTER_COPY策略的关联对象进行处理
    association.acquireValue();

    {
        AssociationsManager manager; // 非全局变量
        
        AssociationsHashMap &associations(manager.get()); // 全局map

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); // 返回的是类对,第二个参数为bool
            if (refs_result.second) {
                /* it's the first association we make */ //标记 isa has_assoc设置为true
                object->setHasAssociatedObjects();
            }

            /* establish or replace the association 建立或者替换关联*/
            auto &refs = refs_result.first->second; //得到空的桶子,找到引用对象类型。
            auto result = refs.try_emplace(key, std::move(association)); //查找当前的key是否有association关联对象
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else { // 如果 value为空则移除关联。
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

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

主要流程为:

  • 1: 创建一个 AssociationsManager 管理类
  • 2: 获取唯一的全局静态哈希Map
  • 3: 判断是否插入的关联值是否存在:
    3.1: 存在走第4步
    3.2: 不存在就走 : 关联对象插入空流程
  • 4: 创建一个空的 ObjectAssociationMap 去取查询的键值对
  • 5: 如果发现没有这个 key 就插入一个 空的 BucketT进去 返回
  • 6: 标记对象存在关联对象
  • 7: 用当前 修饰策略和值 组成了一个 ObjcAssociation 替换原来 BucketT 中的空
  • 8: 标记一下 ObjectAssociationMap 的第一次为 false

关联对象插入空流程:

  • 1: 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 2: 清理迭代器
  • 3: 其实如果插入空值相当于清除

取值流程:

  • 1: 创建一个 AssociationsManager 管理类
  • 2: 获取唯一的全局静态哈希Map
  • 3: 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 4: 如果这个迭代查询器不是最后一个 获取 : ObjectAssociationMap (这里有策略和value) 5: 找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 6: 返回_value
    objc_setAssociatedObject流程.png

相关文章

  • 类拓展和关联对象

    能否向编译好的类中增加实例变量,能否向运行时创建的类中添加实力变量 不能向编译好的类中增加实例变量 只要类没有注册...

  • 类拓展和关联对象

    1. 类拓展和分类 category 类别/分类: 专门用来给类添加新的方法。 不能给类添加成员属性,添加了成员变...

  • 类扩展和关联对象

    分类和类扩展 在OC类的加载[https://www.jianshu.com/p/0d728be5b598]中我们...

  • 对象和类的关联

    1.对象和类的定义 1.1 objc_object 1.2 objc_class 从源码中我们看到objc_cla...

  • iOS 类拓展与分类以及关联对象

    1、类拓展是匿名的分类。2、类拓展可以为类添加属性和方法(类拓展添加的属性和方法是私有的)。3、类拓展在编译时作为...

  • 【iOS】类别和类拓展

    类别(Category)和类拓展(Extension)是Objective-C中独有的用于拓展类对象的机制。 类别...

  • 我所理解的Runtime:3、对象关联和方法交换

    关联对象 14、使用Category对类进行拓展的时候,只能添加方法,而不适合添加属性(可以添加属性,也可以正常使...

  • [iOS] 类扩展和关联对象

    本文主要是针对类的加载的扩展,探索下分类的底层实现原理。 1. 类扩展和分类介绍 1.1 category 类别、...

  • mybatis-association

    关联对象查询(我理解就是对象中有对象 例如 学生类中有 班级的类)

  • runtime之关联对象及方法交换

    关联对象 runtime的对象关联,如果有了解的话就会知道,他是通过一个key,将两个对象或者对象和某个类之间进行...

网友评论

      本文标题:类拓展和关联对象

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