类拓展和关联对象

2020-12-03  本文已影响0人  spades_K

1. 类拓展和分类

category 类别/分类:

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();
}

主要流程为:

关联对象插入空流程:

取值流程:

上一篇 下一篇

猜你喜欢

热点阅读