【iOS开发】——Category底层原理、Extension、关联对象

Category是什么?它可以用来干什么?

Category是Objective-C 2.0之后添加的语言特性,别人口中的分类、类别其实都是指的Category。Category的主要作用是为已经存在的类添加方法。

通常情况下我们可以把类的实现分开在几个不同的文件里面,这样做的好处是:

  • 可以减少单个文件的体积
  • 可以把不同的功能组织到不同的category里
  • 可以由多个开发者共同完成一个类
  • 可以按需加载想要的category
  • 声明私有方法, apple 的SDK中就大面积的使用了category这一特性。比如说:UIKit中的UIView。apple把不同的功能API进行了分类,这些分类包括UIViewGeometryUIViewHierarchyUIViewRendering等。

除此之外,Category还有几个应用场景:

  • 模拟多继承(另外可以模拟多继承的还有protocol
  • framework私有方法公开:什么叫把framework的私有方法公开呢?首先我们都知道类中的私有方法是外界调用不到的,所以假如我们有一个类,类中有一个私有方法A外界不能直接调用,但是假如我们写一个分类并在分类中也声明了一个方法A(我们只声明,但是没有实现)。现在我们如果import这个分类category的话是不是就可以调用分类中的方法A了,实际上这时候就会调用私有方法这个A,我们也就通过分类将私有方法公开化了。

Category特点

  • category只能给某个已有的类扩充方法,不能扩充成员变量。
  • category中也可以添加属性,只不过@property只会生成settergetter的声明,不会生成settergetter的实现以及成员变量。
  • 如果category中的方法和类中原有方法同名,运行时会优先调用category中的方法。 也就是,category中的方法会覆盖掉类中原有的方法。 所以开发中尽量保证不要让分类中的方法和原有类中的方法名相同。避免出现这种情况的解决方案是给分类的方法名统一添加前缀。 比如category_。
  • 如果多个category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。

所以关于方法的调用优先级:
分类(category) > 本类 > 父类。即,优先调用cateory中的方法,然后调用本类方法,最后调用父类方法。
注意⚠️:category是在运行时加载的,不是在编译时。

Category的实质以及实现过程

Category的使用方法十分简单,如果感兴趣可以看看iOS——Category的使用方法,所以我们这里重点讲一下Category的实质以及实现过程,首先我们需要定义一个分类:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface People : NSObject
- (void)talk;

@end

#import "People.h"

@implementation People
- (void)talk {
    NSLog(@"我还不太会说话");
}
@end


#import "People.h"

NS_ASSUME_NONNULL_BEGIN

@interface People (Speak)
-(void)speak;

@end
NS_ASSUME_NONNULL_END



#import "People+Speak.h"

@implementation People (Speak)
- (void)speak {
    NSLog(@"我已经学会说话了");
}
@end

我们将People+Speak.m文件转成C++代码:
首先我们需要cd /进入People+Speak.m文件的上一级文件夹,然后再xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc People+Speak.m这样就会产生一个C++文件

接着我们就可以分析对应的C++源码了

#ifndef _REWRITER_typedef_People
#define _REWRITER_typedef_People
typedef struct objc_object People;
typedef struct {} _objc_exc_People;
#endif

struct People_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
};

// - (void)talk;
/* @end */

#pragma clang assume_nonnull end

#pragma clang assume_nonnull begin
// @interface People (Speak)
// -(void)speak;
/* @end */

#pragma clang assume_nonnull end

// @implementation People (Speak)

static void _I_People_Speak_speak(People * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_hq_s56n6rsn2kq_8lvr0p4_2fvr0000gn_T_People_Speak_2adca8_mi_0);
}
// @end

struct _prop_t {
	const char *name;
	const char *attributes;
};

struct _protocol_t;

struct _objc_method {
	struct objc_selector * _cmd;
	const char *method_type;
	void  *_imp;
};

struct _protocol_t {
	void * isa;  // NULL
	const char *protocol_name;
	const struct _protocol_list_t * protocol_list; // super protocols
	const struct method_list_t *instance_methods;
	const struct method_list_t *class_methods;
	const struct method_list_t *optionalInstanceMethods;
	const struct method_list_t *optionalClassMethods;
	const struct _prop_list_t * properties;
	const unsigned int size;  // sizeof(struct _protocol_t)
	const unsigned int flags;  // = 0
	const char ** extendedMethodTypes;
};

struct _ivar_t {
	unsigned long int *offset;  // pointer to ivar offset location
	const char *name;
	const char *type;
	unsigned int alignment;
	unsigned int  size;
};

struct _class_ro_t {
	unsigned int flags;
	unsigned int instanceStart;
	unsigned int instanceSize;
	const unsigned char *ivarLayout;
	const char *name;
	const struct _method_list_t *baseMethods;
	const struct _objc_protocol_list *baseProtocols;
	const struct _ivar_list_t *ivars;
	const unsigned char *weakIvarLayout;
	const struct _prop_list_t *properties;
};

struct _class_t {
	struct _class_t *isa;
	struct _class_t *superclass;
	void *cache;
	void *vtable;
	struct _class_ro_t *ro;
};

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;
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"speak", "v16@0:8", (void *)_I_People_Speak_speak}}
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_People;

static struct _category_t _OBJC_$_CATEGORY_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"People",
	0, // &OBJC_CLASS_$_People,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_People_$_Speak,
	0,
	0,
	0,
};
static void OBJC_CATEGORY_SETUP_$_People_$_Speak(void ) {
	_OBJC_$_CATEGORY_People_$_Speak.cls = &OBJC_CLASS_$_People;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
	(void *)&OBJC_CATEGORY_SETUP_$_People_$_Speak,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_People_$_Speak,
};
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

我们看一下比较重要的几个地方

Category结构体

【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;//属性列表
};

通过分类结构我们可以看到,分类里可以添加实例方法,类方法,遵循协议,定义属性。此时我们应该会产生一个疑问,为什么没有成员变量列表?这个问题我们后面会解答。

Category结构体赋值

static struct _category_t _OBJC_$_CATEGORY_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"People",
	0, // &OBJC_CLASS_$_People,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_People_$_Speak,
	0,
	0,
	0,
};

我们可以看到如果分类没有添加的东西此时并不会赋值,或者说赋值为0, 因为这个分类我只添加了一个对象方法,所以只给对应的对象方法进行了赋值。

然后就是一个结构体数组

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_People_$_Speak,
};

由此可见分类的底层结构也是结构体,那么我们来看看类的对象调用分类的方法的过程是怎么样的呢

Category的加载处理过程

首先我们要知道Category的加载处理过程是什么样的,主要分为三步,如下所示:

  1. 通过Runtime加载某个类的所有Category数据
  2. 把所有Category的方法、属性、协议数据,合并到一个大数组中 后面参与编译的Category数据,会在数组的前面
  3. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

一步一步来看
首先通过Runtime加载某个类的所有Category数据,那我们就从Runtime初始化函数开始看

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;

    // fixme defer initialization until an objc-using image is found?Fixme延迟初始化直到找到一个使用objc的图像?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

我们可以看到,再经过一系列的初始化方法之口,我们来到 &map_images读取模块(images这里代表模块),来到map_images_nolock函数中找到_read_images函数,在_read_images函数中我们找到分类相关代码:

void 
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    rwlock_writer_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ******  以上代码省略
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
}

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{

   *****省略******
   // Discover categories. 发现类别。
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).Category的目标类缺失(可能是弱链接)。
                // Disavow any knowledge of this category.否认这类知识。
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 这个类别的过程。
            // First, register the category with its target class. 首先,将类别注册到它的目标类中。
            // Then, rebuild the class's method lists (etc) if 然后,重建类的方法列表(等等)
            // the class is realized. 实现了这个类。
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
}

看一下在_read_images函数中,都完成了哪些事情:

  • 首先进入函数以后,先发现了类别,然后通过_getObjc2CategoryList函数获取到分类列表
  • 然后遍历分类列表中的所有分类
  • 获取Category的对应的主类cls,如果没有cls就跳过(continue)这个继续获取下一个,注意编译器还告诉我们了,没有cls有可能是因为弱链接
  • 如果其有对应的主类,并其有实例方法、协议、属性,则调用addUnattachedCategoryForClass,同时如果cls中有实现的话,进一步调用remethodizeClass方法
  • 如果其有对应的主类,并其有类方法、协议,则调用addUnattachedCategoryForClass,同时如果cls的元类有实现的话,就进一步调用remethodizeClass方法

我们需要知道的是,不是所有情况都会调用addUnattachedCategoryForClassremethodizeClass这两个方法的,接下来我们来看一下这两个方法的内部实现:

addUnattachedCategoryForClass

static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    runtimeLock.assertWriting();

    // DO NOT use cat->cls! cls may be cat->cls->isa instead 不要使用cat->cls!CLS可改为cat-> CLS ->isa
    NXMapTable *cats = unattachedCategories();
    category_list *list;

    list = (category_list *)NXMapGet(cats, cls);
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
        list = (category_list *)
            realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    NXMapInsert(cats, cls, list);
}
static NXMapTable *unattachedCategories(void)
{
    runtimeLock.assertWriting();
    //全局对象
    static NXMapTable *category_map = nil;
    if (category_map) return category_map;
    // fixme initial map size 修正初始地图大小
    category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
    return category_map;
}

我们还是先看一下addUnattachedCategoryForClass都做了哪些事情:

  • 通过unattachedCategories()函数生成一个全局map对象cats,编译器还告诉我们:不要使用cat->cls!cls可改为cat-> cls ->isa
  • 我们从这个单例对象cats中查找cls,获取一个category_list *list列表
  • 要是没有list指针。那么我们就生成一个category_list空间
  • 要是有list指针,我们就在该指针的基础上再另外分配category_list所需的大小空间
  • 新分配好空间之后,我们就可以将这个catcatHeader写入
  • 将数据插入到cats中,key----->cls value------>list

addUnattachedCategoryForClass函数并没有去实现分类,在我理解它只是将类和分类联系起来,也就是将类和它的分类对应起来了,因为有这个关系了才可以去依次实现分类,而remethodizeClass才是真正去实现分类的函数

remethodizeClass

static void remethodizeClass(Class cls)
{
    //分类数组
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories 重新分类:查看更多类别
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

我们可以看到函数里又调用了一个很重要的函数attachCategories这个函数接收了类对象cls分类数组cats,我们都知道一个类可以有多个分类。之前我们说到分类信息存储在category_t结构体中,那么多个分类则保存在category_list中

attachCategories

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();
	//根据每个分类中的方法列表,属性列表,协议列表分配内存,列表用来存储分类的方法、属性、协议

    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first 先从倒数数到最新的类别
    int mcount = 0;           // 记录方法的数量
    int propcount = 0;        // 记录属性的数量
    int protocount = 0;       // 记录协议的数量
    int i = cats->count;      // 从分类数组最后开始遍历,保证先取的是最新的分类
    bool fromBundle = NO;     // 记录是否是从 bundle 中取的
    while (i--) { // 从后往前依次遍历,也就是倒数到最新
        auto& entry = cats->list[i];  // 取出当前分类
    
        // 取出分类中的方法列表。如果是元类,取得的是类方法列表;否则取得的是对象方法列表
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;            // 将所有分类中的所有方法存入mlists [ [method_t,method_t] [method_t,method_t] ...... ]
            fromBundle |= entry.hi->isBundle();  // 分类的头部信息中存储了是否是 bundle,将其记住
        }

        // 取出分类中的属性列表,如果是元类,取得的是 nil
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

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

    // 取出当前类 cls 的 class_rw_t 数据
    auto rw = cls->data();

    // 存储方法、属性、协议数组到 rw 中【注意是rw哦】
    // 准备方法列表 mlists 中的方法【为什么需要准备方法列表这一步?】
   
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    // 将新方法列表添加到 rw 中的方法列表中
    rw->methods.attachLists(mlists, mcount);
    // 释放方法列表 mlists
    free(mlists);
    // 清除 cls 的缓存列表
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    // 将新属性列表添加到 rw 中的属性列表中
    rw->properties.attachLists(proplists, propcount);
    // 释放属性列表
    free(proplists);

    // 将新协议列表添加到 rw 中的协议列表中
    rw->protocols.attachLists(protolists, protocount);
    // 释放协议列表
    free(protolists);
}

总结一下attachCategories函数都干什么了

  1. 首先根据方法列表,属性列表,协议列表,malloc分配内存,根据多少个分类以及每一块方法需要多少内存来分配相应的内存地址。
  2. 然后从分类数组里面往三个数组里面存放分类数组里面存放的分类方法,属性以及协议放入对应mlistproplistsprotolosts数组中,这三个数组放着所有分类的方法,属性和协议。
  3. 之后通过类对象的data()方法,拿到类对象的class_rw_t结构体rw,在class结构中我们介绍过,class_rw_t中存放着类对象的方法,属性和协议等数据,rw结构体通过类对象的data方法获取,所以rw里面存放这类对象里面的数据。
  4. 最后分别通过rw调用方法列表、属性列表、协议列表的attachList函数,将所有的分类的方法、属性、协议列表数组传进去,我们可以猜测在attachList方法内部将分类和本类相应的对象方法,属性,和协议进行了合并

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;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0])); //原数据后移
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0])); //拷贝新数据到空出来的内存
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* 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;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

我们可以看到分类的方法属性协议会追加到原来类的方法属性协议列表的前面,这也就是说如果一个类和它的分类有相同的方法,它的分类的方法会先被调用。 具体怎么实现的呢,就是通过memmove将原来的类找那个的方法、属性、协议列表分别进行后移,然后通过memcpy将传入的方法、属性、协议列表填充到开始的位置。 这也就是我们知道的利用分类可以修改源代码中方法里的bug的原因,不是因为原来的方法被替换了,而是因为分类的方法挡在了原有方法的前边,所以只要分类的方法在,就没有原方法的啥事了。

到这里,我们就可以稍微送一口气了,总结一下主要源码流程:

  • objc-os.mm
  • _objc_init
  • map_images
  • map_images_nolock
  • objc-runtime-new.mm
  • _read_images
  • remethodizeClass
  • attachCategories
  • attachLists
  • reallocmemmovememcpy

到此我们总结下category整个流程:我们每创建一个分类,在编译时都会生成category_t这样一个结构体并将分类的方法列表等信息存入_category_t这个结构体。在编译阶段分类的相关信息和本类的相关信息是分开的。等到运行阶段,会通过runtime加载某个类的所有Category数据,把所有Category的方法、属性、协议数据分别合并到一个数组中,然后再将分类合并后的数据插入到本类的数据的前面
到此分类原理就讲完了,接下来我们再讲解下category中的两个方法。

load方法initialize方法

load方法和initialize方法

load调用原理

+load方法会在runtime加载类、分类时调用
每个类、分类的+load方法,在程序运行过程中只调用一次

返回去看一下源码:

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;

    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
//跟进load_images
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here. 如果这里没有+load方法,则不带锁返回。
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods 发现加载方法
    {
        rwlock_writer_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant) 调用+加载方法(没有runtimeLock -重入)
    call_load_methods();
}
//跟进call_load_methods();
void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job. 可重入调用什么也不做;最外层的调用将完成任务。
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1\. Repeatedly call class +loads until there aren't any more 重复调用class +load,直到没有加载为止
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2\. Call category +loads ONCE 
        more_categories = call_category_loads();

        // 3\. Run more +loads if there are classes OR more untried categories 运行更多+负载,如果有类或更多未尝试的类别
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

根据源码我们可以发现,load的调用顺序如下:

load的调用顺序

  • 先调用类的+load
    按照编译先后顺序调用(先编译,先调用)
    调用子类的+load之前会先调用父类的+load
  • 再调用分类的+load
    按照编译先后顺序调用(先编译,先调用)
void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1\. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) { //先调用类的+load
            call_class_loads();
        }

        // 2\. Call category +loads ONCE
        more_categories = call_category_loads(); //再调用分类的+load

        // 3\. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
    int i;

    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;

    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;  //取出classes的method
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, SEL_load); //直接调用
    }

    // Destroy the detached list.
    if (classes) free(classes);
}

+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用的。但也不是每一次的load方法都是这样的,如果我们手动调用load方法,那么就还是会走消息发送机制。

initialize方法

看一下有关源码:

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches

    // Search method lists, try method resolver, etc.
    lookUpImpOrNil(cls, sel, nil, 
                   NO/*initialize*/, NO/*cache*/, YES/*resolver*/);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
     ----- 省略------
    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }
 ----- 省略------

void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;  //是否初始化过

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) { //如果父类未初始化过
        _class_initialize(supercls);  //初始化父类
    }

    // Try to atomically set CLS_INITIALIZING.
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {    //如果自己未初始化
            cls->setInitializing();   //初始化自己
            reallyInitialize = YES;
        }
    }

    if (reallyInitialize) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.

        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);

        if (MultithreadedForkChild) {
            // LOL JK we don't really call +initialize methods after fork().
            performForkChildInitialize(cls, supercls);
            return;
        }

        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                         pthread_self(), cls->nameForLogging());
        }

        // Exceptions: A +initialize call that throws an exception 
        // is deemed to be a complete and successful +initialize.
        //
        // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
        // bootstrapping problem of this versus CF's call to
        // objc_exception_set_functions().
#if __OBJC2__
        @try
#endif
        {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
        }
}

}

// 发送Initialize消息
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

根据最后的源码我们可以看出initialize调用顺序从如下:

  1. 先调用父类的+initialize,再调用子类的+initialize
  2. 先初始化父类,再初始化子类,每个类只会初始化1次

initialize方法的特点:
在这里插入图片描述

load方法和initialize方法的区别与对比

  • 调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend调用
  • 调用时刻:loadruntime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
  • 调用顺序:先调用类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。

文字可以配着图看:
在这里插入图片描述

为什么Category不能添加成员变量?

Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:
typedef struct objc_class *Class;
objc_class结构体的定义如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父类
    const char *name                        OBJC2_UNAVAILABLE;  // 类名
    long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
    long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
    long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表
    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表
#endif
} OBJC2_UNAVAILABLE;

在上面的objc_class结构体中,ivarsobjc_ivar_list(成员变量列表)指针;methodLists是指向objc_method_list指针的指针。在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。 所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。 methodList是一个二维数组,所以可以修改*methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。

Extension

什么是extension

extension被开发者称之为扩展、延展、匿名分类。extension看起来很像一个匿名的category,但是extension和category几乎完全是两个东西。 和category不同的是extension不但可以声明方法,还可以声明属性、成员变量。extension一般用于声明私有方法私有属性私有成员变量

extension的存在形式

category是拥有.h文件和.m文件的东西。但是extension不然。extension只存在于一个.h文件中,或者extension只能寄生于一个类的.m文件中。

category和extension的区别

  • extension在编译的时候,它的数据就已包含在类信息中。category是在运行时,才会将数据合并到类信息中。extension在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,extension伴随类的产生而产生,亦随之一起消亡。
  • extension可以添加成员变量,而category是无法添加成员变量。因为在运行期,对象的内存布局已经确定,如果添加成员变量就会破坏类的内部布局,这对编译型语言来说是灾难性的。
  • extension一般用来隐藏类的私有信息,无法直接为系统的类扩展,但可以先创建系统类的子类再添加extensioncategory可以给系统提供的类添加分类。
  • extensioncategory都可以添加属性,但是category的属性不能生成成员变量和getter、setter方法的实现。

关联对象

associatedObject又称关联对象,把一个对象关联到另外一个对象身上,使两者能够产生联系。关联对象的使用场景最多的是给一个分类增加属性。通过objc_setAssociatedObject 和 objc_getAssociatedObject 方法来设置和获取关联对象。通过 objc_removeAssociatedObjects 移除关联对象。

所以刚学完分类,我们趁热打铁来了解一下关联对象是如何给分类增加属性的:

我们可以在分类中通过@property添加属性,但是不会自动生成私有属性,也不会生成set,get方法的实现,只会生成set,get的声明, 那么set,get方法的实现就需要我们自己去手动完成,怎么实现呢,就是用过关联对象来实现。

先看一个例子:

#import "People.h"
#import "TestProtocol.h"
NS_ASSUME_NONNULL_BEGIN

@interface People (Speak) <TestProtocol>
@property (nonatomic, copy)NSString *testString;

-(void)speak;
+(void)walk;
@end


#import "People+Speak.h"
#import "objc/runtime.h"
@implementation People (Speak)

static char *stringKey = "stingKey";

- (void)setTestString:(NSString *)testString {
    objc_setAssociatedObject(self, stringKey, testString, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)testString {
    id testString = objc_getAssociatedObject(self, stringKey);
    if (testString) {
        return testString;
    } else {
        return nil;
    }
}
@end

我们通过上边的代码可以发现, objc_setAssociatedObjectobjc_getAssociatedObject 这两个方法可以帮我们实现为存在的类添加属性的问题,所以先看看这两个函数内部都做了些什么:

objc_setAssociatedObject

设置关联对象

void objc_setAssociatedObject(id object,const void*key, id value, objc_AssociationPolicy policy)

老规矩,看到函数先了解每个参数的含义都是什么

  • id object:代表的是要给哪个对象添加属性,这里要给自己添加属性,就直接使用self
  • key设置关联对象的key,根据key获取关联对象的属性的值,objc_getAssociatedObject中通过key获得属性的值并返回,这也就是关联对象最重要的关联利用key建立起两者之间的联系
  • id value关联的值,也就是set方法传入的值给属性去保存
  • policy策略,属性以什么形式保存
    我们可以看出来策略一定不是只有一种,那么我们看看策略都有哪些:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           指定一个弱引用相关联对象
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 指定相关的对象的强引用,非原子性
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   指定相关的对象被复制,非原子性
    OBJC_ASSOCIATION_RETAIN = 01401,       指定相关的对象的强引用,原子性
    OBJC_ASSOCIATION_COPY = 01403          指定相关的对象被复制,原子性
};

此刻的策略是为了之后移除做铺垫

objc_getAssociatedObject

获取关联对象

void objc_getAssociatedObject(id object, const void *key)

还是来看参数:

  • id object:获取哪个对象里面的关联的属性 上边的例子中也是self
  • key什么属性,与objc_setAssociatedObject中的key相对应,即通过key值去除value

objc_removeAssociatedObjects

移除关联对象

void objc_removeAssociatedObjects(id object)

就一个参数:

  • id object:移除谁就选择谁

需要注意的是:当对象被释放时,会根据这个策略来决定是否释放关联的对象,当策略是RETAIN/COPY时,会释放(release)关联的对象,当是ASSIGN,将不会释放。
值得注意的是,我们不需要主动调用removeAssociated来接触关联的对象,如果需要解除指定的对象,可以使用setAssociatedObject置nil来实现。

关联对象的底层原理

关联对象并不是存储在被关联对象本身内存中,关联对象是通过AssociationsManager的全局类来做管理。

实现关联对象技术的核心对象有:

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation
    它们之间的关系如图所示:
    关联对象其实使用了两张hashMap,可以用一张图解释他的原理。
    在这里插入图片描述

关联对象底层调用流程:
在这里插入图片描述
总的来说,关联对象主要就是两层哈希map的处理,即存取时都是两层处理,类似于二维数组

接下来我们通过这两幅图来看一下几个比较重要的过程
另外一定要注意AssociationsHashMap 是唯一的,而AssociationsManager不唯一:

class AssociationsManager {
    // associative references: object pointer -> PtrPtrHashMap.
    // AssociationsManager中只有一个变量AssociationsHashMap
    static AssociationsHashMap *_map;
public:
    // 构造函数中加锁
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    // 析构函数中释放锁
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    // 构造函数、析构函数中加锁、释放锁的操作,保证了AssociationsManager是线程安全的
    
    AssociationsHashMap &associations() {
        // AssociationsHashMap 的实现可以理解成单例对象
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

加锁的目的:保证对象的安全性,防止冲突

接着我们看看这个唯一的变量:AssociationsHashMap

    class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };

AssociationsHashMap继承自unordered_map
unordered_map中是以key_typemapped_type作为keyvalue
对应AssociationsHashMap内源码
disguised_ptr_tkey
ObjectAssociationMap *value

ObjectAssociationMap *其实是一个字典,key是从外面传进来的key,那么value呢

class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    }

ObjectAssociationMap中同样以keyvalue的方式存储着ObjcAssociation

现在一步一步找到了ObjcAssociation

class ObjcAssociation {
        uintptr_t _policy;
        // 值
        id _value;
    public:
        // 构造函数
        ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
        // 默认构造函数,参数分别为0和nil
        ObjcAssociation() : _policy(0), _value(nil) {}

        uintptr_t policy() const { return _policy; }
        id value() const { return _value; }
        
        bool hasValue() { return _value != nil; }
    };

我们可以看到ObjcAssociation储着policyvalue这也就是我们在设置关联对象的时候传进来的所以value和policy这两个值最终存储在ObjcAssociation中。

在这里插入图片描述

然后我们回过头看一下最开始的设置关联对象的函数:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

这个很简单就是传参数调用方法
_object_set_associative_reference

// 该方法完成了设置关联对象的操作
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    // 初始化空的ObjcAssociation(关联对象)
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        // 初始化一个manager
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        // 获取对象的DISGUISE值,作为AssociationsHashMap的key
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // value有值,不为nil
            // break any existing association.
            // AssociationsHashMap::iterator 类型的迭代器
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                // 获取到ObjectAssociationMap(key是外部传来的key,value是关联对象类ObjcAssociation)
                ObjectAssociationMap *refs = i->second;
                // ObjectAssociationMap::iterator 类型的迭代器
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    // 原来该key对应的有关联对象
                    // 将原关联对象的值存起来,并且赋新值
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    // 无该key对应的关联对象,直接赋值即可
                    // ObjcAssociation(policy, new_value)提供了这样的构造函数
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                // 执行到这里,说明该对象是第一次添加关联对象
                // 初始化ObjectAssociationMap
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                // 赋值
                (*refs)[key] = ObjcAssociation(policy, new_value);
                // 设置该对象的有关联对象,调用的是setHasAssociatedObjects()方法
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            // value无值,也就是释放一个key对应的关联对象
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    // 调用erase()方法删除对应的关联对象
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    // 释放旧的关联对象
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

需要注意的是,传进来的参数value会经过acquireValue的操作变成一个新的value,那么value经过了什么样的操作变为新的了呢?

// 根据policy的值,对value进行retain或者copy
static id acquireValue(id value, uintptr_t policy) {
    switch (policy & 0xFF) {
    case OBJC_ASSOCIATION_SETTER_RETAIN:
        return objc_retain(value);
    case OBJC_ASSOCIATION_SETTER_COPY:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
    }
    return value;
}

这就是为了以后移除它做铺垫

然后我们再来看一下获取关键对象的方法objc_getAssociatedObject

// 获取关联对象的方法
id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}

同样的传参数调方法_object_get_associative_reference

// 获取关联对象
id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        // 获取到manager中的AssociationsHashMap
        AssociationsHashMap &associations(manager.associations());
        // 获取对象的DISGUISE值
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // 获取ObjectAssociationMap
            ObjectAssociationMap *refs = i->second;
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) {
                // 获取到关联对象ObjcAssociation
                ObjcAssociation &entry = j->second;
                // 获取到value
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                    objc_retain(value);
                }
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    // 返回关联对像的值
    return value;
}

最后就是移除关联对象的方法objc_removeAssociatedObjects

// 移除对象object的所有关联对象
void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}
// 移除对象object的所有关联对象
void _object_remove_assocations(id object) {
    // 声明了一个vector
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        // 如果map size为空,直接返回
        if (associations.size() == 0) return;
        // 获取对象的DISGUISE值
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.复制所有需要删除的关联。
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.删除辅助表。
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.对releaseValue()的调用发生在锁的外部。
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

注意一定要将object对象向对应的所有关联对象全部删除

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值