iOS底层探索 --- Runtime(三)Category底层原理

image

本文章参考自:
iOS 开发:『Runtime』详解(三)Category 底层原理
iOS Category源码探究
本文章不做任何商业用途。优秀的作品要大家一起欣赏,如有疑问请联系删除。

本文用来介绍iOS开发中「Runtime」中的 ·Category· 底层原理。通过本文您将了解到:

  1. Category(分类)简介
  2. Category 的实质
  3. Category 的加载过程
  4. Category(分类)和 Class(类)的+load方法
  5. Category 与关联对象

1、Category(分类)简介

1.1 什么是 Category(分类)?

Category(分类) 是Objective-C 2.0 添加的语言特性,主要作用是为已经存在的类添加方法。Category 可以做到在即不子类化,也不侵入一个类的源码的情况下,为原有的类添加新的方法,从而实现扩展一个类或者分离一个类的目的。在日常开发中,我们经常使用Category为已有的类扩展功能。

虽然继承也能为已有类增加新的方法,而且还能直接增加属性,但是继承增加了不必要的代码复杂度,在运行时,也无法与父类的原始方法进行区分。所以我们可以优先考虑使用自定义的Category(分类)

通常Category(分类)有以下几种使用场景:

  • 把类的不同实现方法分开到不同的文件里。
  • 声明私有方法。
  • 模拟多继承。
  • framework私有方法公开化。

1.2 Category(分类)和 Extension(扩展)

Category(分类)看起来和 Extension(扩展)有点相似。Extension(扩展)有时候也被称为匿名分类。但两者实质上是不同的东西。Extension(扩展)是在编译阶段与该类同时编译的,是类的一部分。而且Extension(扩展)中声明的方法只能在该类的@implementation中实现,这也就意味着,你无法对系统的类(例如NSArray类)使用Extension(扩展)

而且和Category(分类)不同的是,Extension(扩展)不但可以声明方法,还可以声明成员变量,这是Category(分类)所做不到的。

为什么Category(分类)不能像Extension(扩展)一样添加成员变量?

因为Extension(扩展)是在编译阶段与该类同时编译的,就是类的一部分。既然作为类的一部分,且与类同时编译,那么就可以在编译阶段为类添加成员变量。

Category(分类)则不同,Category的特性是:可以在运行时阶段动态的为已有类添加新行为Category是在运行时阶段决定的。而成员变量的内存布局已经在编译阶段确定好了,如果在运行时阶段添加成员变量的话,就会破坏原有类的内存布局,从而造成可怕的后果,所以Categoty无法添加成员变量。


2、Category 的实质

2.1 Category 结构体简介

在iOS底层探索 ---Runtime(一)--- 基础知识中我们知道了:Object (对象)Class (类)的实质分别是objc_object 结构体objc_class 结构体,这里Category也不例外,在objc-runtime-new.h中,Category(分类)被定义为category_t 结构体category_t 结构体的数据结构如下:

struct category_t {
    const char *name; /// 类名
    classref_t cls;   /// 类,在运行时阶段通过 class_name(类名) 对应到类对象
    WrappedPtr instanceMethods;  /// Category 中所有添加的对象方法列表
    WrappedPtr classMethods;     /// Category 中所有添加的类方法列表
    struct protocol_list_t *protocols;                        /// Category 中实现的所有协议列表
    struct property_list_t *instanceProperties;               /// Category 中添加的所有属性
    // 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(分类)的结构体定义中也可以看出,Category(分类)可以为类添加对象方法类方法协议属性。同时,也能发现Category(分类)无法添加成员变量

2.2 Category 的 C++ 源码

想要了解Category的本质,我们需要借助于CategoryC++源码。这种方式我们在探索Block的时候已经使用过了。有兴趣的同学可以参考:Block 底层原理(一)

首先我们建立一个Person类,并添加一个分类Person+Additon;在分类中添加对象方法类方法属性代理
代码如下:

/******************** Person+Addition.h ********************/
#import "Person.h"

NS_ASSUME_NONNULL_BEGIN

@protocol PersonProtocol 

- (void)PersonProtocolMethod;

+ (void)PersonProtocolClassMethod;

@end

@interface Person (Addition)

/* name 属性 */
@property (nonatomic, copy) NSString *personName;

// 类方法
+ (void)printClassName;

// 对象方法
- (void)printName;

@end

NS_ASSUME_NONNULL_END


/******************** Person+Addition.m ********************/
#import "Person+Addition.h"

@implementation Person (Addition)

+ (void)printClassName {
    NSLog(@"printClassName");
}

- (void)printName {
    NSLog(@"printName");
}

#pragma mark - PersonProtocol 方法
- (void)PersonProtocolMethod {
    NSLog(@"PersonProtocolMethod");
}

+ (void)PersonProtocolClassMethod {
    NSLog(@"PersonProtocolClassMethod");
}

@end

在终端执行以下指令,生成.CPP文件:
clang -rewrite-objc Person+Addition.m
下面我们就要分析以下生成的Person+Addition.cpp文件
这里有一个小技巧,生成的代码会有很多,一般从最后一行往前找。

2.2.1 Category 结构体

// Person 类的 Category 结构体
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 类的 Category 结构体赋值
static struct _category_t _OBJC_$_CATEGORY_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Addition,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Addition,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Addition,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Addition,
};

// Category 数组,如果 Person 有多个分类,则 Category 数组中对应多个 Category
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_Person_$_Addition,
};

Category 结构体 源码中我们可以看到:

  1. Category 结构体。
  2. Category 结构体的赋值语句。
  3. Category 结构体数组

第一个Category 结构体2.1 Category 结构体简介中的结构体是一一对应的。可以看做是同一个结构体。第三个Category 结构体数组中存放了Person 类的相关分类,如果有多个分类,则数组中存放对应数目的Category 结构体

2.2.2 Category 中的 「对象方法列表结构体」

// - (void)printName; 对象方法的实现
static void _I_Person_Addition_printName(Person * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_jb_6hgmfgfx44s5vlhxxjmrcxqh0000gn_T_Person_Addition_e519a1_mi_1);
}

// - (void)PersonProtocolMethod; 方法的实现
static void _I_Person_Addition_PersonProtocolMethod(Person * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_jb_6hgmfgfx44s5vlhxxjmrcxqh0000gn_T_Person_Addition_e519a1_mi_2);
}

// Person 分类中添加 「对象方法列表结构体」
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_Person_Addition_printName},
    {(struct objc_selector *)"PersonProtocolMethod", "v16@0:8", (void *)_I_Person_Addition_PersonProtocolMethod}}
};

从「对象方法列表结构体」源码中我们可以看到:

  1. - (void)printName; 对象方法的实现。
  2. - (void)PersonProtocolMethod; 方法的实现。
  3. 对象方法列表结构体

只要是Category中实现了的对象方法(包括代理方法中的对象方法)。都会添加到 对象方法列表结构体 _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Addition中来。如果只是在Person.h中定义,而没有实现,则不会添加。

2.2.3 Category 中的 「类方法列表结构体」

// + (void)printClassName; 类方法的实现
static void _C_Person_Addition_printClassName(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_jb_6hgmfgfx44s5vlhxxjmrcxqh0000gn_T_Person_Addition_e519a1_mi_0);
}

// + (void)PersonProtocolClassMethod; 方法的实现
static void _C_Person_Addition_PersonProtocolClassMethod(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_jb_6hgmfgfx44s5vlhxxjmrcxqh0000gn_T_Person_Addition_e519a1_mi_3);
}

// Person 分类中添加 「类方法列表结构体」
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"printClassName", "v16@0:8", (void *)_C_Person_Addition_printClassName},
    {(struct objc_selector *)"PersonProtocolClassMethod", "v16@0:8", (void *)_C_Person_Addition_PersonProtocolClassMethod}}
};

从「类方法列表结构体」源码中我们可以看到:

  1. + (void)printClassName;类方法的实现。
  2. + (void)PersonProtocolClassMethod; 类方法的实现。
  3. 类方法列表结构体

只要是Category中实现了的类方法(包括代理中的类方法)。都会添加到类方法列表结构体 _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Addition中来。如果只是下Person.h中定义,而没有实现,则不会添加。

2.4 Category 中「协议列表结构体」

// Person 分类中添加 「协议列表结构体」
static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_PROTOCOL_REFS_PersonProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_NSObject
};

// 协议列表 对象方法列表结构体
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_PROTOCOL_INSTANCE_METHODS_PersonProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"PersonProtocolMethod", "v16@0:8", 0}}
};

// 协议列表 类方法列表结构体
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_PROTOCOL_CLASS_METHODS_PersonProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"PersonProtocolClassMethod", "v16@0:8", 0}}
};

// PersonProtocol 结构体赋值
struct _protocol_t _OBJC_PROTOCOL_PersonProtocol __attribute__ ((used)) = {
    0,
    "PersonProtocol",
    (const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_PersonProtocol,
    (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_PersonProtocol,
    (const struct method_list_t *)&_OBJC_PROTOCOL_CLASS_METHODS_PersonProtocol,
    0,
    0,
    0,
    sizeof(_protocol_t),
    0,
    (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_PersonProtocol
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_PersonProtocol = &_OBJC_PROTOCOL_PersonProtocol;

从「协议列表结构体」源码中我们可以看到:

  1. 协议列表结构体。
  2. 协议列表 对象方法列表结构体
  3. 协议列表 *类方法列表结构体
  4. PersonProtocol 协议结构体赋值语句。

2.2.5 Category 中 「属性列表结构体」

// Person 分类中添加的属性列表
static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"personName","T@\"NSString\",C,N"}}
};

从「属性列表结构体」源码中我们可以看到:

只有Person分类中添加的属性列表结构体 _OBJC_$_PROP_LIST_Person_$_Addition,没有成员变量结构体_ivar_list_t 结构体。更没有对应的set 方法 / get 方法相关内容。这也直接说明了Category中不能添加成员变量这一事实。


2.3 Category 的实质总结

Category的本质是_category_t 结构体,其中包含以下几个部分:

  1. _method_list_t类型的「对象方法列表结构体」;
  2. _method_list_t类型的「类方法列表结构体」;
  3. _protocol_list_t类型的「协议列表结构体」;
  4. _prop_list_t类型的「属性列表结构体」。

_category_t 结构体中不包含_ivar_list_t类型,也就是不包含「成员变量结构体」。


3 Category 的加载过程

3.1 dyld的加载流程

我们在iOS底层探索 --- dyld加载流程中详细的探索了dyld的加载流程;而我们之前也说过Category (分类)是在运行时阶段动态加载的;而Runtime (运行时)加载的过程离不开我们的dyld

下面我们就来简单回顾一下dyld的加载流程:
[图片上传失败...(image-193f0f-1622199142993)]

在这里,我们只需要关系执行初始化方法这一步。我们再来回顾一下_objc_init方法:

image

不知道大家还记不记得,我们再探索lldb加载流程的时候,探索到_objc_init方法的时候,找到了关键的一段代码:

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

当时我们探索了第二个参数load_images;而我们的Category的加载流程就藏在第一个参数map_images里面。按照我们之前的探索方式,持续的跟进,我们会发现这样一条函数调用栈:
_objc_init --> map_images --> map_images_nolock --> _read_images
到这里的时候,我并没有发现关于Category的代码,于是搜索了一下,在_read_images方法里面发现了这个:

image

那么就进入load_categories_nolock看一下,突然觉得找对了地方,里面有很多关于关于Category的内容,下面我们一起来探索一下:

static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    size_t count;
    auto processCatlist = [&](category_t * const *catlist) {
        for (unsigned i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
            locstamped_category_t lc{cat, hi};

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Ignore the category.
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category.
            if (cls->isStubClass()) {
                // Stub classes are never realized. Stub classes
                // don't know their metaclass until they're
                // initialized, so we have to add categories with
                // class methods or properties to the stub itself.
                // methodizeClass() will find them and add them to
                // the metaclass as appropriate.
                if (cat->instanceMethods ||
                    cat->protocols ||
                    cat->instanceProperties ||
                    cat->classMethods ||
                    cat->protocols ||
                    (hasClassProperties && cat->_classProperties))
                {
                    objc::unattachedCategories.addForClass(lc, cls);
                }
            } else {
                // First, register the category with its target class.
                // Then, rebuild the class's method lists (etc) if
                // the class is realized.
                if (cat->instanceMethods ||  cat->protocols
                    ||  cat->instanceProperties)
                {
                    if (cls->isRealized()) {
                        attachCategories(cls, &lc, 1, ATTACH_EXISTING);
                    } else {
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                }

                if (cat->classMethods  ||  cat->protocols
                    ||  (hasClassProperties && cat->_classProperties))
                {
                    if (cls->ISA()->isRealized()) {
                        attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                    } else {
                        objc::unattachedCategories.addForClass(lc, cls->ISA());
                    }
                }
            }
        }
    };

    processCatlist(hi->catlist(&count));
    processCatlist(hi->catlist2(&count));
}

这里看到attachCategories这个函数,跟进去(注意看官方注释,这个很重要,顿时豁然开朗,这就是要找的核心代码呀!!!):

image

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     *
     * Categories must be added in the proper order, which is back
     * to front. To do that with the chunking, we iterate cats_list
     * from front to back, build up the local buffers backwards,
     * and call attachLists on the chunks. attachLists prepends the
     * lists, so the final result is in the expected order.
     */
    /// 创建方法列表、属性列表、协议列表,用来存储分类的方法,属性,协议
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;        /// 记录方法的数量
    uint32_t propcount = 0;     /// 记录属性的数量
    uint32_t protocount = 0;    /// 记录协议的数量
    bool fromBundle = NO;       /// 记录是否是从 bundle 中提取的
    bool isMeta = (flags & ATTACH_METACLASS);

    /// auto是C++的语法,表示 类型根据代码自动推断
    /// cls->data()->extAllocIfNeeded() 返回的就是 'class_rw_ext_t *' 类型
    /// class_rw_ext_t 中存放有 '方法'、'属性'、'协议'等信息。('extAllocIfNeeded' 跟进去就可以看到)
    /*
        struct class_rw_ext_t {
            DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
            class_ro_t_authed_ptr ro;
            method_array_t methods;
            property_array_t properties;
            protocol_array_t protocols;
            char *demangledName;
            uint32_t version;
        };
    */
    auto rwe = cls->data()->extAllocIfNeeded();

    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i]; ///取出当前分类;entry -> locstamped_category_t

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta); ///取出分类中的方法
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            /// 根据上面可以知道 ‘ATTACH_BUFSIZ = 64’
            /// 也就是说这句话等于 ‘mlists[64 - ++mcount] = mlist;’
            /// 倒叙合并分类的 mlists, 后面会将 mlists 再合并到类的方法列表中
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;

            /// 分类的头部信息中储存了是否是 bundle,将其记录
            fromBundle |= entry.hi->isBundle();
        }

        /// 取出分类中的属性列表,如果是元类,取得的是 nil
        /*
            property_list_t *
            category_t::propertiesForMeta(bool isMeta, struct header_info *hi)
            {
                if (!isMeta) return instanceProperties;
                else if (hi->info()->hasCategoryClassProperties()) return _classProperties;
                else return nil;
            }
        */
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        ///取出分类中遵循的协议列表
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        ///将 mlists 合并到 rwe->methods
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

看上面代码的最后两行,可以看到协议和属性调用了同一个方法attachLists

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            ///根据下面的代码,可以看出来,分类中的方法会被添加到原有方法的前面。
            ///在方法查询的时候,分类中的方法会先被查到并返回。
            ///如果分类中定义了和原类中一样的方法,那么只会执行分类中的方法。
            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            for (unsigned i = 0; i < addedCount; i++)
                array()->lists[i] = addedLists[i];
            validate();
        }
    }

这里有一点很重要,注意看我在attachLists中的注释。不过我还是要再强调一遍:
分类中的方法会被添加到原有方法的前面。
在方法查询的时候,分类中的方法会先被查到并返回。
如果分类中定义了和原类中一样的方法,那么只会执行分类中的方法。

由此可见,Category在运行时被加载的流程如下:
_objc_init --> map_images --> map_images_nolock --> _read_images --> attachCategories --> attachLists


4 Category(分类)和 Class(类)的+load方法

Category(分类)中的方法属性协议附加到上面的操作,是在+load方法执行之前进行的。也就是说+load方法执行之前,中就已经加载了Category(分类)中的方法属性协议

Category(分类)Class(类)+load方法的调用顺序规则如下所示:

  1. 先调用主类,按照编译顺序,依次根据继承关系,由父类向子类调用;
  2. 调用完主类,再调用分类,按照编译顺序,依次调用;
  3. +load方法除非主动调用,否则只会调用一次。

通过这样的调用规则,我们可以知道:主类的+load方法一定在分类+load方法之前调用。但是分类的+load方法调用顺序并不是按照继承关系调用的,而是按照编译顺序确定的,这一导致了+load方法的调用顺序并不确定。
可能是:父类 -> 子类 -> 父类分类 -> 子类分类
也可能是:父类 -> 子类 -> 子类分类 -> 父类分类


5 Category 与 关联对象

上面我们说过,在Category中虽然可以添加属性,但是不会生成对应的成员变量,也不能生成gettersetter方法。因此调用Category中声明的属性时会报错。

那么有什么办法可以解决这个问题吗?还记得我们在iOS底层探索 --- Runtime(二)Method Swizzling使用到的关联对象吗?这就是一个很好的解决办法。下面我们一起来回顾一下:

我们可以自己来实现gettersetter方法,并借助关联对象(Objcticve-C Associated Objects来实现gettersettter方法。关联对象能够帮助我们在运行时阶段将任意的属性关联到一个对象上。具体要用到方法如下:

///1、通过 key : value 的形式给对象 object 设置关联属性
void objc_setAssociatedObject(id  _Nonnull NSObject, const void * _Nonnull key, id  _Nullable value, objc_AssociationPolicy policy);

///2、通过 key 获取关联的属性 object
id objc_getAssociatedObject(id  _Nonnull object, const void * _Nonnull key);

///3、移除对象所关联的属性
void objc_removeAssociatedObjects(id  _Nonnull object);

详细内容可以参考:关联对象 AssociatedObject 完全解析

5.1 UIImage 分类中增加网络地址属性

/****************** UIImage+Property.h ******************/
#import 

NS_ASSUME_NONNULL_BEGIN

@interface UIImage (Property)

///图片网络地址
@property (nonatomic, copy) NSString *urlString;

///用于清除关联对象
- (void)clearAssociatedObject;

@end

NS_ASSUME_NONNULL_END



/****************** UIImage+Property.m ******************/
#import "UIImage+Property.h"
#import 

@implementation UIImage (Property)

/// set 方法
- (void)setUrlString:(NSString *)urlString {
    objc_setAssociatedObject(self, @selector(urlString), urlString, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

/// get方法
- (NSString *)urlString {
    return  objc_getAssociatedObject(self, @selector(urlString));
}

/// 清除关联对象
- (void)clearAssociatedObject {
    objc_removeAssociatedObjects(self);
}

@end

⚠️ 注意:使用objc_removeAssociatedObjects可以断开所有的关联。通常情况下不建议使用,因为它会断开所有的关联。如果想要断开关联,可以使用objc_setAssociatedObject,将关联对象设置成nil
比如我们上面的清除关联对象可以这样写:objc_setAssociatedObject(self, @selector(urlString), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

你可能感兴趣的:(iOS底层探索 --- Runtime(三)Category底层原理)