RunTime解析--Category(分类)实现

2024-02-28 20:10

本文主要是介绍RunTime解析--Category(分类)实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在日常的开发中,我们经常用到分类,用来给一个类添加扩展,包括对象方法、类方法、当然我们还可以通过关联对象的方式给分类添属性。但是这中间的过程是如何实现的呢?下面我们来做一个详细的解释。

结构category_t

我们先看下分类的结构:

// 分类
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;// methodsForMeta 返回类方法列表或者对象方法列表method_list_t *methodsForMeta(bool isMeta) {if (isMeta) return classMethods;else return instanceMethods;}// 属性列表返回方法property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

从上面我们看到,一个分类的结构体中,包含我们最关心的几个结构体:instanceMethods,classMethods,protocols,instanceProperties,而对于_classProperties目前好像大多数文章在讲解时都将他选择性的忽略了,我们在本篇文章结束后在继续看下这个属性到底是什么有什么作用!

从上面的结构我们看出:分类是独立于原始类存在的。因此对于分类来说也肯定是有一块单独的内存空间来存放分类,那么到底是怎么存放的呢?我们下面来看下

分类的存储

我们先用CLang命令看下分类的结构和实现,我们新建一个类PrimaryObject以及他的分类PrimaryObject (Demo)

.h

@interface PrimaryObject : NSObject- (void)test;@end@interface PrimaryObject (Demo)@end

.m

@implementation PrimaryObject- (void)test {NSLog(@"PrimaryObject---test");
}@end@implementation PrimaryObject (Demo)- (void)test {NSLog(@"PrimaryObject-Demo---test");
}@end

然后我们对.m文件使用clang命令看下生成的cpp结果,具体命令如下

clang -rewrite-objc MyClass.m

此时会在当前目录下生成一个PrimaryObject.cpp文件,我们来看下这个文件,我们可以通过我们的关键词定位到下面这段代码

// 对象方法列表
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_PrimaryObject_$_Demo __attribute__ ((used, section ("__DATA,__objc_const"))) = {// 对应结构体中的entsizesizeof(_objc_method),// 对应结构体中的method_count1,// 对应结构中的_objc_method  方法名 参数{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_PrimaryObject_Demo_test}}
};// 类方法列表
static struct /*_method_list_t*/ {unsigned int entsize;  // sizeof(struct _objc_method)unsigned int method_count;struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_PrimaryObject_$_Demo __attribute__ ((used, section ("__DATA,__objc_const"))) = {// 对应结构体中的entsize 与对象方法相同sizeof(_objc_method),// 对应结构体中的method_count1,// 对应结构中的_objc_method 方法名 参数{{(struct objc_selector *)"classMethod", "v16@0:8", (void *)_C_PrimaryObject_Demo_classMethod}}
};// 属性列表
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_PrimaryObject_$_Demo __attribute__ ((used, section ("__DATA,__objc_const"))) = {// entsizesizeof(_prop_t),// count_of_properties1,// _prop_t{{"demoCategoryArray","T@\"NSArray\",&,N"}}
};extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_PrimaryObject;// 一个静态分类 结构体 名字为_OBJC_$_CATEGORY+分类名
static struct _category_t _OBJC_$_CATEGORY_PrimaryObject_$_Demo __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
// 类名"PrimaryObject",0, // &OBJC_CLASS_$_PrimaryObject,// 对象方法列表(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_PrimaryObject_$_Demo,// 类方法列表(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_PrimaryObject_$_Demo,0,// 属性列表(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_PrimaryObject_$_Demo,
};// 
static void OBJC_CATEGORY_SETUP_$_PrimaryObject_$_Demo(void ) {_OBJC_$_CATEGORY_PrimaryObject_$_Demo.cls = &OBJC_CLASS_$_PrimaryObject;
}#pragma section(".objc_inithooks$B", long, read, write)__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {(void *)&OBJC_CATEGORY_SETUP_$_PrimaryObject_$_Demo,
};
static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {&OBJC_CLASS_$_PrimaryObject,
};static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {&_OBJC_$_CATEGORY_PrimaryObject_$_Demo,
};static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

首先编译器生成了实例方法列表_OBJC_$_CATEGORY_INSTANCE_METHODS_PrimaryObject_$_Demo和类方法列表_OBJC_$_CATEGORY_CLASS_METHODS_PrimaryObject_$_Demo以及属性列表_OBJC_$_PROP_LIST_PrimaryObject_$_Demo。三者的命名都遵循了公共前缀+类名+category名字的命名方式,而且实例方法列表和类方法列表里面填充的正是我们在Demo这个category里面写的方法classMethodclassMethod,而属性列表里面填充的也正是我们在Demo里添加的demoCategoryArray属性。还有一个需要注意到的事实就是category的名字用来给各种列表以及后面的category结构体本身命名,而且有static来修饰,所以在同一个编译单元里我们的category名不能重复,否则会出现编译错误。

其次,编译器生成了category本身_OBJC_$_CATEGORY_PrimaryObject_$_Demo,并用前面生成的列表来初始化category本身。

最后,编译器在DATA段下的objc_catlist section里保存了一个大小为1的category_t的数组L_OBJC_LABELCATEGORY$,在DATA段下的__objc_classlist section里保存了一个大小为1的_category_t数组L_OBJC_LABEL_CLASS_

从上面Clang的代码中我们看到,分类的结构是独立于主类存在的,那么分类与主类是怎么关联的呢?分类又是如何加载的呢?

分类的加载

我们首先看下rumtime的入口方法

_objc_init
void _objc_init(void)
{static bool initialized = false;if (initialized) return;initialized = true;// fixme defer initialization until an objc-using image is found?
//	读取runtime的环境变量,如果需要则打印出来environ_init();tls_init();//运行c++ 构造函数static_init();//lock 初始化 暂时是空的函数lock_init();//初始化系统异常操作exception_init();/*仅供objc运行时使用,注册在映射、取消映射和初始化objc映像调用的处理程序。dyld将使用包含objc-image-info回调给`mapped`.这些dylibs将自动引用计数,因此objc将不再需要调用dlopen()防止未加载。在调用_dyld_objc_notify_register()期间,dyld将调用 `mapped` 在已经加载好 images,稍后dlopen()。在调动init的时候也会调用`mapped`,在dyld调用的时候,也会调用init函数在调用任何images +load方法时候*/_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

出去上面的init方法我们看到,dyld的朱啊哟做操是调用了_dyld_objc_notify_register方法,我们接着看下这个方法做了什么参数有代表什么意思。

_dyld_objc_notify_register
// 仅供objc运行时使用
// Note: only for use by objc runtime
// 当objc镜像在映射、取消映射、和初始化时注册的事件会被调
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld会回调给将mapped函数一组包含objc-image-info段
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// 这些dylibs 将自动引用计数,因此objc将不再需要调用dlopen()防止未加载
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded.  During the call to _dyld_objc_notify_register(),
// 在调用_dyld_objc_notify_register()期间 dyld将调用 `mapped` 在已经加载好 images,稍后dlopen()。
// dyld will call the "mapped" function with already loaded objc images.  During any later dlopen() call,
// 在调动init的时候也会调用`mapped`,在dyld调用的时候,也会调用init函数
// dyld will also call the "mapped" function.  Dyld will call the "init" function when dyld would be called
// 在调用任何images +load方法时候
// initializers in that image.  This is when objc calls any +load methods in that image.
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,_dyld_objc_notify_init      init,_dyld_objc_notify_unmapped  unmapped);

总的来说上面这个方法的作用是:向dyld注册监听Mach-O中OC相关section被加载入\载出内存的事件,而具体的事件为:

  • _dyld_objc_notify_mapped 当dyld已将images加载入内存时回调。调用map_images方法
  • _dyld_objc_notify_init 当dyld初始化image后。OC调用类的+load方法,就是在这时进行的
  • _dyld_objc_notify_unmapped 当dyld将images移除内存时。调用unmap_image方法

我们先来看下第一个方法,上面我们知道这个方法会在dyld将image加到内存中后调用map_images方法:

map_images

// dyld将image加到内存中后调用
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方法,因为这里我们主要关注的是分类的加载因此与分类加载无关的代码我们暂时先屏蔽不看

map_images_nolock
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],const struct mach_header * const mhdrs[])
{   // 当头文件个数大于0调用_read_images方法if (hCount > 0) {_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);}}

map_images_nolock方法中主要是调用了_read_images方法 我们来深入的看下这个方法

_read_images
// 
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{header_info *hi;uint32_t hIndex;size_t count;size_t i;Class *resolvedFutureClasses = nil;size_t resolvedFutureClassCount = 0;static bool doneOnce;TimeLogger ts(PrintImageTimes);runtimeLock.assertLocked();#define EACH_HEADER \hIndex = 0;         \hIndex < hCount && (hi = hList[hIndex]); \hIndex++if (!doneOnce) {doneOnce = YES;if (DisableTaggedPointers) {disableTaggedPointers();}initializeTaggedPointerObfuscator();if (PrintConnecting) {_objc_inform("CLASS: found %d classes during launch", totalClasses);}// namedClasses// Preoptimized classes don't go in this table.// 4/3 is NXMapTable's load factorint namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;gdb_objc_realized_classes =NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);ts.log("IMAGE TIMES: first time tasks");}// Discover classes. Fix up unresolved future classes. Mark bundle classes.for (EACH_HEADER) {classref_t *classlist = _getObjc2ClassList(hi, &count);if (! mustReadClasses(hi)) {// Image is sufficiently optimized that we need not call readClass()continue;}bool headerIsBundle = hi->isBundle();bool headerIsPreoptimized = hi->isPreoptimized();for (i = 0; i < count; i++) {Class cls = (Class)classlist[i];Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);if (newCls != cls  &&  newCls) {// Class was moved but not deleted. Currently this occurs // only when the new class resolved a future class.// Non-lazily realize the class below.resolvedFutureClasses = (Class *)realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class));resolvedFutureClasses[resolvedFutureClassCount++] = newCls;}}}ts.log("IMAGE TIMES: discover classes");// Fix up remapped classes// Class list and nonlazy class list remain unremapped.// Class refs and super refs are remapped for message dispatching.if (!noClassesRemapped()) {for (EACH_HEADER) {Class *classrefs = _getObjc2ClassRefs(hi, &count);for (i = 0; i < count; i++) {remapClassRef(&classrefs[i]);}// fixme why doesn't test future1 catch the absence of this?classrefs = _getObjc2SuperRefs(hi, &count);for (i = 0; i < count; i++) {remapClassRef(&classrefs[i]);}}}ts.log("IMAGE TIMES: remap classes");// Fix up @selector referencesstatic size_t UnfixedSelectors;{mutex_locker_t lock(selLock);for (EACH_HEADER) {if (hi->isPreoptimized()) continue;bool isBundle = hi->isBundle();SEL *sels = _getObjc2SelectorRefs(hi, &count);UnfixedSelectors += count;for (i = 0; i < count; i++) {const char *name = sel_cname(sels[i]);sels[i] = sel_registerNameNoLock(name, isBundle);}}}ts.log("IMAGE TIMES: fix up selector references");#if SUPPORT_FIXUP// Fix up old objc_msgSend_fixup call sitesfor (EACH_HEADER) {message_ref_t *refs = _getObjc2MessageRefs(hi, &count);if (count == 0) continue;if (PrintVtables) {_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch ""call sites in %s", count, hi->fname());}for (i = 0; i < count; i++) {fixupMessageRef(refs+i);}}ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif// Discover protocols. Fix up protocol refs.for (EACH_HEADER) {extern objc_class OBJC_CLASS_$_Protocol;Class cls = (Class)&OBJC_CLASS_$_Protocol;assert(cls);NXMapTable *protocol_map = protocols();bool isPreoptimized = hi->isPreoptimized();bool isBundle = hi->isBundle();protocol_t **protolist = _getObjc2ProtocolList(hi, &count);for (i = 0; i < count; i++) {readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle);}}ts.log("IMAGE TIMES: discover protocols");// Fix up @protocol references// Preoptimized images may have the right // answer already but we don't know for sure.for (EACH_HEADER) {protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);for (i = 0; i < count; i++) {remapProtocolRef(&protolist[i]);}}ts.log("IMAGE TIMES: fix up @protocol references");// Realize non-lazy classes (for +load methods and static instances)for (EACH_HEADER) {classref_t *classlist = _getObjc2NonlazyClassList(hi, &count);for (i = 0; i < count; i++) {Class cls = remapClass(classlist[i]);if (!cls) continue;// hack for class __ARCLite__, which didn't get this above
#if TARGET_OS_SIMULATORif (cls->cache._buckets == (void*)&_objc_empty_cache  &&  (cls->cache._mask  ||  cls->cache._occupied)) {cls->cache._mask = 0;cls->cache._occupied = 0;}if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache  &&  (cls->ISA()->cache._mask  ||  cls->ISA()->cache._occupied)) {cls->ISA()->cache._mask = 0;cls->ISA()->cache._occupied = 0;}
#endifaddClassTableEntry(cls);realizeClass(cls);}}ts.log("IMAGE TIMES: realize non-lazy classes");// Realize newly-resolved future classes, in case CF manipulates themif (resolvedFutureClasses) {for (i = 0; i < resolvedFutureClassCount; i++) {realizeClass(resolvedFutureClasses[i]);resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);}free(resolvedFutureClasses);}    ts.log("IMAGE TIMES: realize future classes");// Discover categories.// #define EACH_HEADER hIndex = 0; hIndex < hCount && (hi = hList[hIndex]); hIndex++for (EACH_HEADER) {//GETSECT(_getObjc2CategoryList,        category_t *,    "__objc_catlist");// 从__objc_catlist获取方法列表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).// 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;// 如果这个类的instanceMethods、protocols、instanceProperties有值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);}}}}ts.log("IMAGE TIMES: discover categories");// Category discovery MUST BE LAST to avoid potential races // when other threads call the new category code before // this thread finishes its fixups.// +load handled by prepare_load_methods()if (DebugNonFragileIvars) {realizeAllClasses();}#undef EACH_HEADER
}

在这段代码中出现频次比较高的是EACH_HEADER显然这是一个for循环中的条件,我们来看下每次循环都做了什么

for (EACH_HEADER) {classref_t *classlist = _getObjc2ClassList(hi, &count);
}for (EACH_HEADER) {Class *classrefs = _getObjc2ClassRefs(hi, &count);
}for (EACH_HEADER) {SEL *sels = _getObjc2SelectorRefs(hi, &count);
}for (EACH_HEADER) {message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
}
for (EACH_HEADER) {protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
}for (EACH_HEADER) {protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
}
for (EACH_HEADER) {classref_t *classlist = _getObjc2NonlazyClassList(hi, &count);
}
for (EACH_HEADER) {category_t **catlist = _getObjc2CategoryList(hi, &count);
}

经过这样的简化后,我们在通过这些循环中调用的方法来判断这些循环中到底做了什么,在找这些方法的时候我们发现这些方法实际上在runtime中有单独的定义的地方

//      function name                 content type     section name
GETSECT(_getObjc2SelectorRefs,        SEL,             "__objc_selrefs"); 
GETSECT(_getObjc2MessageRefs,         message_ref_t,   "__objc_msgrefs"); 
GETSECT(_getObjc2ClassRefs,           Class,           "__objc_classrefs");
GETSECT(_getObjc2SuperRefs,           Class,           "__objc_superrefs");
GETSECT(_getObjc2ClassList,           classref_t,      "__objc_classlist");
GETSECT(_getObjc2NonlazyClassList,    classref_t,      "__objc_nlclslist");
GETSECT(_getObjc2CategoryList,        category_t *,    "__objc_catlist");
GETSECT(_getObjc2NonlazyCategoryList, category_t *,    "__objc_nlcatlist");
GETSECT(_getObjc2ProtocolList,        protocol_t *,    "__objc_protolist");
GETSECT(_getObjc2ProtocolRefs,        protocol_t *,    "__objc_protorefs");
GETSECT(getLibobjcInitializers,       UnsignedInitializer, "__objc_init_func");

这实际上是rumtime是根据Mach-O各个section的信息来初始化其自身,结合上面我们说过分类的结构体和分类中的对象方法被添加到__DATA__下的__objc_catlist中,类方法被添加到__objc_classlist中,而属性则是被添加到__objc_const中。

下面我们先来看下__objc_catlist,同时我们定位到对应的for循环

__objc_catlist
        // 遍历所有分类for (i = 0; i < count; i++) {// 取出每一个分类category_t *cat = catlist[i];// 调用remapClass(cat->cls),并返回一个objc_class *对象cls。这一步的目的在于找到到category对应的类对象clsClass cls = remapClass(cat->cls);if (!cls) {// Category's target class is missing (probably weak-linked).// 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;}bool classExists = NO;// 如果这个类的instanceMethods、protocols、instanceProperties有值// 如果Category中有实例方法,协议,实例属性,会改写target class的结构if (cat->instanceMethods ||  cat->protocols  ||  cat->instanceProperties) {//addUnattachedCategoryForClass(cat, cls, hi);//修改class的method list结构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" : "");}}// 如果category中有类方法,协议,或类属性(目前OC版本不支持类属性), 会改写target class的元类结构if (cat->classMethods  ||  cat->protocols  ||  (hasClassProperties && cat->_classProperties)) {addUnattachedCategoryForClass(cat, cls->ISA(), hi);// 修改class的method list结构if (cls->ISA()->isRealized()) {remethodizeClass(cls->ISA());}if (PrintConnecting) {_objc_inform("CLASS: found category +%s(%s)", cls->nameForLogging(), cat->name);}}}}

不管是在分类中添加的是对象方法还是类方法,我们发现实际上调用的都是addUnattachedCategoryForClass方法,那么我们看下这个方法是如何实现的呢?

addUnattachedCategoryForClass
// 把类和category做一个关联映射
//  category_t *cat 分类
//  Class cls 原始类
//  header_info *catHeader 分类头信息
static void addUnattachedCategoryForClass(category_t *cat, Class cls, header_info *catHeader)
{runtimeLock.assertLocked();// DO NOT use cat->cls! cls may be cat->cls->isa instead// 初始化一个分类表(这里是一个table)NXMapTable *cats = unattachedCategories();// 分类listcategory_list *list;// 获取原始类cls元有的分类表list = (category_list *)NXMapGet(cats, cls);// 如果之前没有 那么新建if (!list) {list = (category_list *)calloc(sizeof(*list) + sizeof(list->list[0]), 1);} else {// 如果之前已经有了 那么扩容list->count + 1list = (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);
}

将分类与原始类做了映射后,都调用remethodizeClass方法,我们看下这个方法做了什么

remethodizeClass
static void remethodizeClass(Class cls)
{category_list *cats;bool isMeta;runtimeLock.assertLocked();isMeta = cls->isMetaClass();// 取出还未被附加到class上的category listif ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {if (PrintConnecting) {_objc_inform("CLASS: attaching categories to class '%s' %s", cls->nameForLogging(), isMeta ? "(meta)" : "");}// 将category附加到class上attachCategories(cls, cats, true /*flush caches*/);        free(cats);}
}

上面这个方法,我们看到runtime会首先通过unattachedCategoriesForClass 取出还未被附加到class上的category list,然后调用attachCategories将这些category附加到class上。

所以主要的逻辑在attachCategories中,我们来看下这个方法

attachCategories
// 链接 分类中的 方法列表 属性和协议 到类中
// Class cls  原始类
// category_list *cats 分类列表
// bool flush_caches 是否需要刷新缓存
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 *, property_list_t *, protocol_list_t *的数组空间,数组大小等于category的个数//方法数组 二维数组 每个分类的方法是数组中的一个元素 每个分类可能有多个方法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 firstint mcount = 0;int propcount = 0;int protocount = 0;// i表示分类的个数int i = cats->count;bool fromBundle = NO;//依次读取每一个category,将其methods,property,protocol添加到mlists,proplist,protolist中存储while (i--) {//取出某个分类auto& entry = cats->list[i];//取出分类 的 对象方法或者类方法method_list_t *mlist = entry.cat->methodsForMeta(isMeta);// 将方法列表加入都mlists中(注意mlist本身也是个数组) 因此mlists是一个二维数组if (mlist) {mlists[mcount++] = mlist; //mlists 接受所有分类方法fromBundle |= entry.hi->isBundle();}//proplist 所有分类属性property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi);if (proplist) {proplists[propcount++] = proplist;}//proplist 所有协议方法protocol_list_t *protolist = entry.cat->protocols;if (protolist) {protolists[protocount++] = protolist;}}// 取出class的data()数据,其实是class_rw_t * 指针,其对应结构体实例存储了class的基本信息auto rw = cls->data();prepareMethodLists(cls, mlists, mcount, NO, fromBundle);//将category中的method 添加到class中rw->methods.attachLists(mlists, mcount);//释放数组free(mlists);// 如果需要,同时刷新class的method list cacheif (flush_caches  &&  mcount > 0) flushCaches(cls);// 将category的property添加到class中rw->properties.attachLists(proplists, propcount);//释放数组free(proplists);// 将category的protocol添加到class中rw->protocols.attachLists(protolists, protocount);//释放数组free(protolists);
}

attachCategories对类和元类都生效,即不论我们是向类对象中添加对象方法还是向元类中添加类方法都是调用attachCategories方法,在方法中我们明确的看到方法属性协议分别添加到原始类的列表中,当然我们还发现不管是方法属性还是协议我们调用的都是attachLists方法,下面我们来进一步看看这个方法。

在分析attachLists方法之前我们先看下方法属性协议对应的类型:


struct class_rw_t {method_array_t methods;//方法列表property_array_t properties;//属性列表protocol_array_t protocols;//协议列表

但实际上method_array_t,property_array_t,protocol_array_t都是对list_array_tt类型的包装。而attachLists刚好是list_array_tt中的方法。

attachLists
 // 要添加的list 要添加的个数void attachLists(List* const * addedLists, uint32_t addedCount) {if (addedCount == 0) return;if (hasArray()) {// 就list的个数uint32_t oldCount = array()->count;// 添加后list的个数uint32_t newCount = oldCount + addedCount;//分配内存 内存不够用了,需要扩容setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));//赋值countarray()->count = newCount;// array()->lists:原来的方法列表向后移动 oldCount * sizeof(array()->lists[0]个长度memmove(array()->lists + addedCount/*数组末尾*/, array()->lists/*数组*/,oldCount * sizeof(array()->lists[0])/*移动的大小*/);//空出来的 内存使用addedLists拷贝过去 大小是:addedCount * sizeof(array()->lists[0])memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));}// 如果list为空 且要添加的个数为1 那么直接设置else if (!list  &&  addedCount == 1) {// 0 lists -> 1 listlist = addedLists[0];} else {// 如果要添加的个数大于1个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)// 将旧的数据放在了数组的addedCount的位置// 前面addedCount个位置是为了给addedLists保留array()->lists[addedCount] = oldList;// 将addedLists拷贝到数组的0-addedCount的位置memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));}}// memmove :内存移动。
/*  __dst : 移动内存的目的地
*   __src : 被移动的内存首地址
*   __len : 被移动的内存长度
*   将__src的内存移动__len块内存到__dst中
*/
void    *memmove(void *__dst, const void *__src, size_t __len);// memcpy :内存拷贝。
/*  __dst : 拷贝内存的拷贝目的地
*   __src : 被拷贝的内存首地址
*   __n : 被移动的内存长度
*   将__src的内存移动__n块内存到__dst中
*/
void    *memcpy(void *__dst, const void *__src, size_t __n);

结合上图,我们发现将新的list插入到原有list的方式是:头部插入,结合方法调用的时候查找元类或者类对象的方法列表来调用,我们知道,分类的方法是会覆盖原始类的方法。这也从侧面证实了这一点。

上面的代码我们看到,无论是方法列表还是属性列表或者是协议列表我们都会添加到原始类对应的列表中,但是在实际使用中,我们都知道分类是无法添加属性的,这又是为什么呢?

注意:上面的这句描述实际上是有问题的,准确的描述应该是分类可以添加属性但是系统不会为这个属性动态添加setter和getter方法(以及创建一个以_开头的实例变量)。

AssociatedObject

不过runtime还是提供了关联属性的方法,让我们可以在分类中添加属性(实例变量+getter+setter),下面我们来看下关联属性的实现:

id objc_getAssociatedObject(id object, const void *key) {return _object_get_associative_reference(object, (void *)key);
}void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {_object_set_associative_reference(object, (void *)key, value, policy);
}void objc_removeAssociatedObjects(id object) 
{if (object && object->hasAssociatedObjects()) {_object_remove_assocations(object);}
}

在看这几个方法的具体实现之前,我们先看下policy参数的值

objc_AssociationPolicy
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {/**< Specifies a weak reference to the associated object. */OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a strong reference to the associated object. *   The association is not made atomically. */OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies that the associated object is copied. *   The association is not made atomically. */OBJC_ASSOCIATION_COPY_NONATOMIC = 3,/**< Specifies a strong reference to the associated object.*   The association is made atomically. */OBJC_ASSOCIATION_RETAIN = 01401,    /**< Specifies that the associated object is copied.*   The association is made atomically. */   OBJC_ASSOCIATION_COPY = 01403          
};

下面我们依次看下这三个方法,首先_object_set_associative_reference设置关联属性。

_object_set_associative_reference
// 设置关联对象的值
// object 目标对象
// 关联对象的key
// 关联对象的值
// 关联策略
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);//获取new_value的值// acquireValue根据policy== OBJC_ASSOCIATION_SETTER_COPY 或者OBJC_ASSOCIATION_SETTER_RETAIN// 判断是要进行retain操作还是copy操作id new_value = value ? acquireValue(value, policy) : nil;{AssociationsManager manager;//生成一个全局的 HashMapAssociationsHashMap &associations(manager.associations());disguised_ptr_t disguised_object = DISGUISE(object);//newValue存在 需要更新if (new_value) {// 遍历 hashMap是否有该objAssociationsHashMap::iterator i = associations.find(disguised_object);// 遍历if (i != associations.end()) {//这个对象有关联对象 注意这里取出的并不是某个对象 而是一个哈希表ObjectAssociationMap *refs = i->second;// 根据key找到对应的关联对象ObjectAssociationMap::iterator j = refs->find(key);// 遍历根据key的查找结果if (j != refs->end()) {// 如果找到了 更新值old_association = j->second;j->second = ObjcAssociation(policy, new_value);} else {// 如果找到最后仍然没有找到 那么新增插入(*refs)[key] = ObjcAssociation(policy, new_value);}} else {//如果这个对象之前就没有关联对象 那么新建ObjectAssociationMap *refs = new ObjectAssociationMap;associations[disguised_object] = refs;(*refs)[key] = ObjcAssociation(policy, new_value);object->setHasAssociatedObjects();}} else {// 如果传入的关联对象值为nil,则断开关联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);}}}}// 释放旧值if (old_association.hasValue()) ReleaseValue()(old_association);
}

大概的流程是:

  • 根据关联的policy,调用id new_value = value ? acquireValue(value, policy) : nil; ,acquireValue 方法会根据poilcy是retain或copy,对value做引用+1操作或copy操作,并返回对应的new_value。(如果传入的value为nil,则返回nil,不做任何操作)
  • 获取到new_value 后,根据是否有new_value的值。如果 new_value 存在,则对象与目标对象关联。实质是存入到全局单例 AssociationsManager manager 的对象关联表中。 如果new_value 不存在,则释放掉之前目标对象及关联 key所存储的关联对象。实质是在 AssociationsManager 中删除掉关联对象。
  • 最后,释放掉之前以同样key存储的关联对象

从上面的代码中我们看到所有对象的关联属性实际上都存放在一个全局的静态哈希表中static AssociationsHashMap *_map; 然后分别根据对象地址取出对象所有的关联属性,进而根据key去获取对应的关联对象。大致结构如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sesy1GhK-1604154248536)(http://vanney9.com/lionheart/1706/association.png)]

接着我们在来看下获取的方法即_object_get_associative_reference方法:

_object_get_associative_reference
// 获取一个已关联的对象
id _object_get_associative_reference(id object, void *key) {id value = nil;uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;{// 可以把manager理解为一个单例 实际上是一个静态变量AssociationsManager manager;//初始化 _map = AssociationsHashMapAssociationsHashMap &associations(manager.associations());// 取反object 地址 作为accociative keydisguised_ptr_t disguised_object = DISGUISE(object);// 在关联对象海西表中 找到与disguised_object关联的所有对象AssociationsHashMap::iterator i = associations.find(disguised_object);// 遍历这个哈希表if (i != associations.end()) {// 取出当前的这个关联对象ObjectAssociationMap *refs = i->second;// 根据key查找对应的关联对象 有可能存在多个ObjectAssociationMap::iterator j = refs->find(key);// 遍历查找结果if (j != refs->end()) {// 取出关联实体ObjcAssociation &entry = j->second;value = entry.value();policy = entry.policy();if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {objc_retain(value);}}}}// 如果policy = OBJC_ASSOCIATION_GETTER_AUTORELEASEif (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {objc_autorelease(value);}return value;
}

结合上面图中的存储结构,get方法的流程为:

  • 根据对象地址获取该对象所有的关联属性
    AssociationsHashMap::iterator i = associations.find(disguised_object);

  • 根据关联对象的key获取到具体的关联对象并返回 同时根据该关联对象的策略 判断是否需要retain

紧接着我们再来看下释放的方法objc_removeAssociatedObjects,首先我们先搜索一下这个方法的调用时机,

objc_destructInstance
void *objc_destructInstance(id obj) 
{if (obj) {Class isa = obj->getIsa();if (isa->hasCxxDtor()) {object_cxxDestruct(obj);}if (isa->instancesHaveAssociatedObjects()) {_object_remove_assocations(obj);}objc_clear_deallocating(obj);}return obj;
}

objc_destructInstance方法是对象在被释放时调用的,那么objc_removeAssociatedObjects的时机就是在对象被释放时,如果发现这个对象有关联对象那么就去释放这个对象的关联对象。

具体的释放方法如下:

_object_remove_assocations
// 对象释放时移除所有的关联属性
void _object_remove_assocations(id object) {vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());if (associations.size() == 0) return;disguised_ptr_t disguised_object = DISGUISE(object);// 根据对象地址取出所有的关联对象AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {// 拷贝所有的关联关系ObjectAssociationMap *refs = i->second;for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {// 记录所有的关联对象到elementselements.push_back(j->second);}// 删除refs 删除这个对象的所有关联对象delete refs;// 清除关联关系associations.erase(i);}}// elements数组中的所有元素执行ReleaseValue操作for_each(elements.begin(), elements.end(), ReleaseValue());
}

删除关联对象的时候,我们实际上是将某个对象的所有关联对象放到一个数组中,先清除关联关系和,然后在对数组中的所有元素执行release操作。

通过上面的讲述,我们知道了分类方法是如何被添加到原始类的方法列表、属性列表、协议列表中的。而且添加的时机为map_images(参考前面讲述的_objc_init方法)。

那么我们接着看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.if (!hasLoadMethods((const headerType *)mh)) return;recursive_mutex_locker_t lock(loadMethodLock);// Discover load methods{mutex_locker_t lock2(runtimeLock);//加载 class+load 和category+load方法prepare_load_methods((const headerType *)mh);}// Call +load methods (without runtimeLock - re-entrant)//执行 class+load 和category+load方法call_load_methods();
}

我们看到load_images方法中主要是执行了call_load_methods方法,这也侧面证明在+load方法中我们可以使用关联对象,因为对应的属性和方法列表已经被添加到类中。

而对于call_load_methods方法:

call_load_methods
// 执行+load方法
void call_load_methods(void)
{do {//执行class+load直到完成while (loadable_classes_used > 0) {call_class_loads();}// 执行Category +load 一次more_categories = call_category_loads();// 3. Run more +loads if there are classes OR more untried categories} while (loadable_classes_used > 0  ||  more_categories);
}

我们也看到,+load方法是先调用了原始类中的+load方法,再调用分类中的load方法。

总结

这篇文章我们主要介绍了分类的结构和分类是如何与主类关联的,同时分析了关联对象方法的具体实现。同时在介绍load_images方法时,我们又验证了+load方法的调用时机以及与分类方法链接到主类的执行顺序。

这篇关于RunTime解析--Category(分类)实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/756493

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P