Category、load和initialize方法

1,269

1、Category的实现结构体

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);

    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};

每添加一个分类,编译器就会帮我们生成一个category_t的结构体。App在启动的时候,

调用_objc_init方法进行runtime初始化,并调用_read_images()方法进行加载所有的类和分类。大意源码如下:

void _objc_init(void)
{
    // 加载所有类、分类等
    _read_images()
    // 初始化所有类、分类等
    load_images()
}

_read_images源码实现

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    // ...
    // 加载所有的分类
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }
    // ...
}

// 将分类的信息附加到类
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags) {

    // 将分类的方法、协议、属性等信息附加给类,并通过memmove、memcpy方法附加到类原本信息的前面
    // 所以分类的方法调用优先于类本身的方法
}

load_images源码实现

void load_images(const char *path __unused, const struct mach_header *mh)
{
    // 初始化所有的类以及它的父类
    prepare_load_methods((const headerType *)mh);

    // 调用
    call_load_methods();
}

// 初始化所有的类以及它的父类
void prepare_load_methods(const headerType *mhdr)
{
    // ...
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // 递归调用类和所有未加载的父类的+load方法
        schedule_class_load(remapClass(classlist[i]));
    }
    // ...
}

static void schedule_class_load(Class cls)
{
    // ...
    // 如果有父类,则递归调用加载父类
    schedule_class_load(cls->superclass);
    // 加载当前类
    add_class_to_loadable_list(cls); 
    // ...
}

void add_class_to_loadable_list(Class cls)
{
    // ...
    // 将类和类的load方法存储到已加载的数组中
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

Category、load小结

category分类底层实现是一个category_t结构体,在运行时才会将分类信息合并到类中

在类和分类结构初始化后,会调用所有的类和分类load方法。调用顺序如下:

  1. 先调用类本身的load方法,如果有父类,则递归调用父类的load方法
  2. 接下来调用分类的load方法,分类的load方法按照编译顺序进行调用

load方法调用是直接使用函数地址进行调用

Category添加属性

@interface Person : NSObject 

// 在类中添加一个实例变量,等同于给类加了一个_age的成员变量
// 以及age的get、set方法声明和实现
@property (nonatomic, assign) NSInteger age;

@end

@interface Person (Eat)

// 在分类中声明一个实例变量,等同于给类多加了count的get和set方法声明
// 但是没有添加_count的成员变量和get、set方法的实现
@property (nonatomic, assign) NSInteger count;

@end

// =================================================================
#import <objc/runtime.h>

@implementation Person (Eat)

static const char kPersonCountKey;

// 使用runtime设置关联属性
// key可以使用 
// ① _cmd (表示当前方法的selector)
// ② selector   ==> @selector(name)
// ③ 常量字符串   ==> @"name"
// ④ 静态char常量 static const char 
- (void)setCount:(NSInteger)count {
    objc_setAssociatedObject(self, &kPersonCountKey, @(count), OBJC_ASSOCIATION_ASSIGN);
}

- (NSInteger)count {
    return [objc_getAssociatedObject(self, &kPersonCountKey) integerValue];
}

- (void)removeAll {
    // 移除跟当前对象的所有关联属性
    objc_removeAssociatedObjects(self);
}

@end

2、关联对象

实现关联对象的核心类

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation
class AssociationsManager {
    static AssociationsHashMap *map;
}

typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;

class ObjcAssociation {
    uintptr_t _policy;
    id _value;
}

  • 关联对象不是存储在被关联对象的内存中,而是存储在全局统一的一个AssociationsHashMap中。所有的关联对象都由AssociationsManager统一管理
  • 在设置关联对象时,以当前对象的内存地址作为key,找到当前对象对应的AssociationsHashMap。不同的对象对应不同的AssociationsHashMap
  • 然后在AssociationsHashMap中,根据关联对象的key,找到对应的ObjectAssociationMap。不同的关联对象,对应不同的ObjectAssociationMap
  • ObjectAssociationMap中存储的是关联对象的内存管理策略和值

问:为什么关联对象的内存策略中没有weak对象呢?

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

问:关联对象什么时候释放?

关联对象在类执行dealloc方法时,会移除跟当前类有关的全部关联对象。

3、initialize

initialize是在第一个给类发送消息时,才会调用。

因为initialize是通过objc_msgSend方式进行调用的。

查找流程:

class_getInstanceMethod

👇

lookUpImpOrForward

👇

initializeNonMetaClass

void initializeNonMetaClass(Class cls)
{
    Class supercls;
    bool reallyInitialize = NO;

    // 递归调用父类的initialize方法
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }

    // ...

    callInitialize(cls);

    // ...
}

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

initialize总结

  • 每个类只会initialize一次,但是initialize是使用objc_msgSend方式进行调用的,
  • 所以如果子类没有实现+ (void)initialize ,会调用父类的+ (void)initialize方法,所以父类的+ (void)initialize会被调用多次。
  • 父类的+ (void)initialize先于子类调用。