0.前言
日常开发中经常会用到 Category,对于其使用方法就不多做说明了,本篇主要介绍其底层实现原理。
1.底层结构
1.1 编译后的结构
依然从一个例子开始,给 HHStaff
这个类创建一个分类 HHStaff+CateA
,如下所示:
// HHStaff+CateA.h
@interface HHStaff (CateA) <NSCopying, NSCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger num;
- (void)methodA1;
- (void)methodA2;
+ (void)classMethodA1;
+ (void)classMethodA2;
@end
// HHStaff+CateA.m
@implementation HHStaff (CateA)
- (void)methodA1 {
NSLog(@"这是 methodA1");
}
- (void)methodA2 {
NSLog(@"这是 methodA2");
}
+ (void)classMethodA1 {
NSLog(@"这是 classMethodA1");
}
+ (void)classMethodA2 {
NSLog(@"这是 classMethodA2");
}
@end
然后终端执行 $ clang -rewrite-objc HHStaff+CateA.m
编译后,我们发现了这样一个结构:
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 在内存中的基本结构,其中包括其所属主类的类名、分类中的实例方法列表、类方法列表、协议方法列表以及属性列表。我们自己写的分类 HHStaff+CateA
编译后是这样的:
static struct _category_t _OBJC_$_CATEGORY_HHStaff_$_CateA __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"HHStaff",
0, // &OBJC_CLASS_$_HHStaff,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_HHStaff_$_CateA,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_HHStaff_$_CateA,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_HHStaff_$_CateA,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_HHStaff_$_CateA,
};
上边的 _OBJC_$_CATEGORY_HHStaff_$_CateA
就是自己添加的那个分类 HHStaff+CateA
编译后的结构,它是 struct _category_t
类型的,也就是上边提到的分类的基本结构。
下边这张图详细介绍了编译后的结构:
1.2 OC 源码中的结构
在 objc 的源码中全局搜索 category_t, 最终在 objc-runtime-new.h
中发现了它的结构,如下所示:
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;
}
// 获取属性列表的方法(声明):对象属性or类属性
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
上边结构中,注释行以上部分的结构与我们编译后文件中的结构体 category_t
完全一致,注释行以后部分中的 _classProperties
(类属性列表) 没有接触过,后期如果遇到了再补上 O(∩_∩)O~
2.Category 合并到 Class
从上边一节我们了解到 Category 经编译后是一个与 Class 相互独立的结构,那么其中的方法、属性等信息是如何和主类联系起来的呢?其实,这一切都是在运行时发生的。下面我就开始深入 runtime 源码来探究这个相互关联的过程。
2.1 源码分析
objc 的入口函数是 objc-os.mm
中的 _objc_init()
,源码如下:
void _objc_init(void)
{
// 使用一个静态局部变量,避免了重复初始化
static bool initialized = false;
if (initialized) return;
initialized = true;
// 初始化操作
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
// 注册 dyld 事件的监听
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
此函数的最后一行注册了 3 个 dyld 事件的监听,分别是:
- 当有 image(镜像) 被 dyld mapped 的时候,执行回调函数
map_images()
; - 当有 image(镜像) 被加载的时候,执行回调函数
load_images()
; - 当有 image(镜像) 将要被 dyld unmap 的时候,执行回调函数
unmap_image()
。
其中,将 Category 中的信息整合到 class 里边的操作就在 map_images()
这个函数里边,下面我们看一下它的源码:
void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
//
return map_images_nolock(count, paths, mhdrs);
}
这里又嵌套调用了 map_images_nolock()
这个函数,为了排除干扰,这里略去了其他代码,精简后的结构如下:
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
// ... 此处略去 n 多字 ...
if (hCount > 0) {
// 读取镜像,参数:hList 即 headerList,hCount 即 headerCount。
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
// ... 此处略去 n 多字 ...
}
hCount 即头文件的个数,也就是说有新的头文件,才会执行后边的 _read_images()
函数,各参数的含义从名称就可以看得出来。
下面看看精简后的读取镜像的函数 _read_images()
:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
// ...
// Discover categories.
for (EACH_HEADER) {
// 1.得到一个存放着分类的地址的一维数组
category_t **catlist = _getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
// 2.获取数组中每一个 category_t 类型的指针及 Category 所属的主类
category_t *cat = catlist[i]; // cat 是一个指向结构体 category_t 的指针
Class cls = remapClass(cat->cls); // Category 所属的主类
// ...
// 3.处理 Category
// 3.1 类对象
bool classExists = NO;
if (cat->instanceMethods || cat->protocols || cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls); // cls 是类对象,重新方法化,重新组织类里边的方法
classExists = YES;
}
// ...
}
// 3.2 元类对象
if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA()); // cls->ISA() 是元类对象,重新方法化,重新组织元类里边的方法
}
// ...
}
} // 内层 for
} // 外层 for
// ...
}
这里有 2 层 for 循环,首先看最外边的 for 循环,其中 EACH_HEADER
这个宏的定义为:
#define EACH_HEADER (hIndex = 0; hIndex < hCount && (hi = hList[hIndex]); hIndex++)
也就是依次取出 hList 里的每一个 header ,执行后边的操作,后边的操作大概可以分为 3 项:
- 取出存放着分类地址的一维数组
catlist
; - 获取
catlist
中每一个 category_t 类型的指针cat
及 Category 所属的主类cls
; - 处理 Category,依次针对
类对象
和元类对象
重新组织其中的方法,即remethodizeClass(cls)
或remethodizeClass(cls->ISA())
。
remethodizeClass()
这个函数的作用是将 Category 中的信息添加到 class 里边,其源码如下:
// Attach outstanding categories to an existing class.
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
// ...
// 将分类中的方法、属性及协议添加到 class 里边
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
上边的 if 条件语句中执行了 attachCategories()
这个函数,它的作用是将分类中的方法、属性及协议添加到 class 里边,源码如下:
static void attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
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;
// 1.整合一个类中所有分类的方法数组、属性数组和协议数组
while (i--) {
auto& entry = cats->list[i]; // 取出一个分类
// 1.1 方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist; // 一次循环就将一个分类的方法列表添加到 mlists 里边,作为一个元素。
fromBundle |= entry.hi->isBundle();
}
// 1.2 属性
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist; // 与上边类似
}
// 1.3 协议
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist; // 与上边类似
}
}
// 2.将上一步整合的数组添加到类结构里边
auto rw = cls->data();
// 2.1 方法
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
// 2.2 属性
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
// 2.3 协议
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
这里主要做了 2 件事:
- 整合一个类的每一个分类的方法数组(属性数组/协议数组)到一个二维数组里边,二维数组的每一个元素就是一个分类的方法列表(属性列表/协议列表);
- 执行 class 中的 data() 方法返回一个可读可写的结构,它的类型是一个结构体
class_rw_t
,这个结构体在上一篇已经介绍过。然后,执行attachLists()
函数,将上一步整合的方法数组添加到class_rw_t
这个结构体里边。
需要注意的是,在整合那一步 while 循环中,条件是 i--,而 i 是分类的个数 (int i = cats->count;),也就是从后往前取,即先取后编译的分类。
现在我们来看上方代码中最后调用的核心方法 attachLists()
:
void attachLists(List* const * addedLists, uint32_t addedCount) { // addedLists 是二维数组,addedCount 是数组元素个数 [[], [], []]
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
// 1.扩容
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); // 根据 newCount 重新分配数组
array()->count = newCount;
// 2.移动老数组到末尾
memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0]));
// 3.拷贝新数组到头部
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]));
}
}
此方法主要做了以下三件事(原数组:类中原来的方法(属性、协议等)数组,新数组:category中整合的方法(属性、协议)数组):
- 原数组扩容
realloc()
- 移动原数组到末尾
memmove()
- 拷贝新数组到扩容后数组的头部
memcpy()
(以方法为例)
至此,分类中的方法、属性、协议就整合到了类结构里边。
根据前文的讨论,分类中的方法会在运行时和主类中的方法整合到一起,并且分类中的方法会放在前边(多个分类的话,后编译的在前边)。那么当查找方法的时候,就会优先查找分类的方法,如果分类和主类有相同的方法,主类中的方法就不会执行,也就是平时所说的 “覆盖” 主类方法,其实主类中的对应方法还是存在的,只是没机会执行而已。
2.2 一种特殊情况
既然 Category 中的同名方法会 "覆盖" 主类中的方法,这个时候,怎么才能执行主类中原有的方法?
笔者曾经被问及这个问题,居然没有答上来 ′⌒` ,后来找到了一位前辈提供的解决方案,在此引用一下(添加了少量注释):
Class currentClass = [MyClass class];
MyClass *my = [[MyClass alloc] init];
if (currentClass) {
unsigned int methodCount;
Method *methodList = class_copyMethodList(currentClass, &methodCount);
IMP lastImp = NULL;
SEL lastSel = NULL;
for (NSInteger i = 0; i < methodCount; i++) {
Method method = methodList[i];
NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method))
encoding:NSUTF8StringEncoding];
if ([@"要执行的方法名" isEqualToString:methodName]) {
lastImp = method_getImplementation(method);
lastSel = method_getName(method);
}
}
typedef void (*fn)(id,SEL); // 如果有参数,也可以在定义中添加
if (lastImp != NULL) {
fn f = (fn)lastImp;
f(my,lastSel); // 如果有参数,也可以在这里传递
}
free(methodList);
}
所谓 "覆盖",其实只是把 Category 中的方法放到了方法列表的前边,上边代码的逻辑就是按顺序查找方法数组,找到最后一个对应方法(即主类中原有方法)并执行。
3. 相关问题扩展
3.1 load
我们都知道 runtime 在加载类和分类时会调用 +load 方法,那么具体是怎么调用的,这就要从前文提到的回调函数 load_images()
开始说起了,这个方法的主要作用就是执行 load
方法。
void load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// 1.Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// 2.Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
这个方法主要干了 2 件事:①准备 load 方法;②执行 load 方法。下面分别介绍一下这两件事:
3.1.1 准备 load 方法
准备 load 方法的工作主要是通过下边这个函数 prepare_load_methods()
执行的:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 1.组织类里边的 load 方法
schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
// 2.将分类添加到 loadable_list
add_category_to_loadable_list(cat);
}
}
上方函数总共做了两件事:①组织类里边的 load 方法;②将分类添加到 loadable_list,现在分别看看具体干了什么。
3.1.1.1 组织类里边的 load 方法
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// 1.递归调用当前函数
schedule_class_load(cls->superclass);
// 2.将含有 load 方法的类及对应的 load 方法存储到 loadable_list 里边
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
这里先做了一次递归调用,然后将传入的 cls
加到 loadable_list
里边,这样做的效果是:先将父类加进 loadable_list
,再加子类。
那么具体是怎么添加的呢,下面的源码将告诉我们真相:
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
// 1.获取 load 方法,如果 cls 没有 load 方法,就不往下执行了
method = cls->getLoadMethod();
if (!method) return;
// ...
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)realloc(loadable_classes, loadable_classes_allocated *sizeof(struct loadable_class));
}
// 2.保存 cls 和 method 至 loadable_classes
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++; // loadable_class 的数量自加,遍历执行 load 方法时会用到
}
这里也做了两件事:
- 查找 load 方法,源码如下,可以看出来,是通过比较字符串的方法找 load 方法的。
IMP objc_class::getLoadMethod()
{
runtimeLock.assertLocked();
const method_list_t *mlist;
assert(isRealized());
assert(ISA()->isRealized());
assert(!isMetaClass());
assert(ISA()->isMetaClass());
// 便利方法列表,查找 load 方法
mlist = ISA()->data()->ro->baseMethods();
if (mlist) {
for (const auto& meth : *mlist) {
const char *name = sel_cname(meth.name);
// 比较字符串
if (0 == strcmp(name, "load")) {
return meth.imp;
}
}
}
return nil;
}
- 将 load 方法和对应的 class 组合成
loadable_class
存储到loadable_classes
里边。其中loadable_classes
是个一维数组,loadable_class
是一个结构体,它的定义如下,
struct loadable_class {
Class cls; // may be nil
IMP method;
};
3.1.1.2 将分类添加到 loadable_list
添加分类至 loadable_list 是通过 add_category_to_loadable_list()
函数实现的,源码如下:
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
// 1.获取分类中的 load 方法,如果没有,就不往下执行
method = _category_getLoadMethod(cat);
if (!method) return;
// ...
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)realloc(loadable_categories, loadable_categories_allocated *sizeof(struct loadable_category));
}
// 2.保存分类和 load 方法至 loadable_categories 里边
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
基本原理与上边的 add_class_to_loadable_list()
相同,区别在于这里是添加到了 loadable_categories
里边,并且被添加的是 loadable_category
,定义如下。
struct loadable_category {
Category cat; // may be nil
IMP method;
};
3.1.2 执行 load 方法
前边已经准备好了 load 方法,现在就该调用了,也就是执行下边的 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();
// *** 调用 load 方法
do {
// 1.调用所有类的 +load 方法
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2.调用所有分类的 +load 方法
more_categories = call_category_loads();
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
此方法也是做了两件事:先
调用所有类
的 load 方法,后
调用所有分类
的 load 方法。
3.1.2.1 调用类的 load 方法
下边是执行类的 load 方法的函数 call_class_loads()
,主要是遍历 loadable_classes 类里边的每一个 loadable_class,取出其中存储的函数指针,直接去调用函数。也就是说,没有走 objc_msgSend() 的流程,而是直接通过函数地址调用函数,那么也就不存在 “覆盖” 的问题。
static void call_class_loads(void)
{
int i;
// 1.拿到 loadable_classes 及元素个数 loadable_classes_used
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// 2.遍历 loadable_classes,调用每一个元素的 load 方法
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
// ...
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
3.1.2.2 调用分类的 load 方法
调用分类的 load 方法的操作都在下面的 call_category_loads()
函数里边。
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
// ...
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
// ...
return new_categories_added;
}
仔细观察上边的函数,就会发现它和 call_class_loads()
的逻辑基本一致,这里就不多做说明了。
通过上边的讨论我们可以得出调用 +load
方法的顺序:
① 调用类的 +load
根据编译顺序调用(先编译,先调用)
调用子类的 +load
之前会先调用父类的 +load
② 调用分类的 +load
根据编译顺序调用(先编译,先调用)
3.1.2.3 验证 load 方法的执行顺序
为了验证上文所述 +load
的执行顺序,这里创建了两个类 MyClass
和 YourClass
,及其各自的 2 个分类,他们都实现了 +load
,并在其中打印了类或分类的名称。
运行 demo,然后调整编译顺序后,再重新运行,控制台输出如下,仔细观察发现,结果与上边查看源码得出的结论一致 O(∩_∩)O~
3.2 initialize
众所周知,+initialize
会在类第一次接收到消息时调用,那么它到底是怎么调用的呢?
我们可以先按照这样的思路来考虑,OC 的方法调用经编译后都会变成这样的函数调用 objc_msgSend(object, @selector(method))
,于是可以推断 objc_msgSend()
会调用 +initialize
,而且应该做了判断,如果调用过了一次,就不再重复调用。但是搜索 objc 源码后发现 objc_msgSend()
是用汇编实现的 (⊙﹏⊙)b,看着有点累。
另外,我们知道执行方法时,会先根据 isa 指针找到对应的类对象或元类对象,然后查找需要的方法。objc 的源码中有 class_getInstanceMethod()
和 class_getClassMethod()
2 个函数,根据字面意思判断应该是获取方法的函数,那么
+initialize
应该也在这里调用,现在就以 class_getInstanceMethod()
为例,验证一下我们的推断。
下面是函数 class_getInstanceMethod()
的源码:
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
#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);
}
注意到上边的函数中有一个搜索方法列表的函数 lookUpImpOrNil()
,其实现如下(做了适当精简):
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
{
// ...
// 1.优先查找缓存
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
// 2.执行 initialize 方法
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst)); // 重点
runtimeLock.lock();
}
// 3.后边是查找方法步骤,这里就不罗列了...
}
该方法主要做了下面几件事:
1.优先查找缓存 (Optimistic cache lookup)
2.如果需要执行 +initialize
,并且当前类没有执行过 +initialize
,就去执行 +initialize
3.后边是查找方法步骤:①查缓存;②查方法列表;③查父类的缓存和方法列表
第 2 件事执行 +initialize
方法时,调用的是 _class_initialize()
这个函数,源码如下,同样做了适当精简:
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// 1.递归调用父类的 +initialize
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) {
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
// ...
#if __OBJC2__
@try
#endif
{
// 2.调用 +initialize
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
// ...
return;
} else if (cls->isInitializing()) {
// ...
} else if (cls->isInitialized()) {
// ...
} else {
// ...
}
// ...
}
这段代码说明类的 +initialize
方法的执行顺序是,先递归调用父类的 +initialize
,然后调用自己的 +initialize
,最终的执行如下所示:
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
上边最终执行的是 objc_msgSend()
这个函数,也就是说还是会根据 isa 和 super 查找方法,也就存在下边这种特殊情况了。
Note that +initialize is sent to the superclass (again) if this class doesn't implement +initialize. 2157218
如果当前类没有实现 +initialize 方法,那么就会再调用一次父类的 +initialize 方法
3.3 关联对象
由于分类的底层结构中没有成员变量,所以也就无法通过分类添加成员变量,但是,我们都知道可以通过关联对象的方式添加属性,从而实现有成员变量的效果O(∩_∩)O~,通常的做法是这样的:
// HHClass+Category.h
@interface HHClass (Category)
@property (nonatomic, copy) NSString *myName;
@end
// HHClass+Category.m
static const void * MyNameKey = &MyNameKey;
@implementation HHClass (Category)
- (void)setMyName:(NSString *)myName {
objc_setAssociatedObject(self, MyNameKey, myName, OBJC_ASSOCIATION_COPY_NONATOMIC);
// objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy);
}
- (NSString *)myName {
return objc_getAssociatedObject(self, MyNameKey);
// objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
}
@end
虽然上边的方式可以达到在 Category 中添加属性的目的,但是对于关联的对象在内存中的存储方式,我们还是不清楚,下面我们从 objc_setAssociatedObject)
这个函数的源码入手,来研究一下这个问题。
// objc-runtime.m
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 old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
// 1.根据传入的值是否为 nil,确定做保存操作还是删除操作
if (new_value) { // 1.1 保存,如果传入的值不为空
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 2.根据是否到了 associations 的末尾(是否能找到),确定做添加操作还是替换操作
if (i != associations.end()) {
// 根据 disguised_object 取出 ObjectAssociationMap
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
// 3.根据是否到了 associations 的末尾(是否能找到),确定做添加操作还是替换操作
if (j != refs->end()) { // 3.1 若没到 ObjectAssociationMap 的结尾,替换
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else { // 3.2 若到了 ObjectAssociationMap 的结尾,添加
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else { // 1.2 擦除,如果传入的值为 nil
// setting the association to nil breaks the association.
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;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
从上方源码可以看出来,关联对象存储在 AssociationsHashMap
这样一个结构里,其中,key 就是传入的 object 的地址 DISGUISE(object),value 是一个 ObjectAssociationMap
,其中 key 就是传入的 key,value 是一个 ObjcAssociation
,里边存储着传入的 value 和 policy,其对应关系如下图所示。
🍎 一点想法:关联对象需要在被关联对象 dealloc 的时候手动释放么,理由是什么?
其实想回答这个问题,只需要查看一下 - (void)dealloc
的源码就行了,层层深入 - (void)dealloc
方法的源码,会发现调用了这样一个函数:
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj); // 🍎 移除关联对象
obj->clearDeallocating();
}
return obj;
}
也就是说,系统在调用 - (void)dealloc
的时候,会先检查并移除关联对象,既不需要我们手动去操作,至于具体怎么操作,点开 _object_remove_assocations()
就知道了😈(得再补充点 C++ 知识了)。