先抛出来一些问题
Category中可以不通过关联来添加属性吗?
Category是如何被加载的?
父类的load方法会先于子类调用、如何实现的 ?
在类的load方法中可以调用在Category中声明的方法吗?
Category简介
category是Objective-C 2.0之后添加的语言特性,category的主要作用是为已经存在的类添加方法。
Category常用的使用场景:
(1) 可以把类的实现分开在几个不同的文件里面、这样做有几个显而易见的好处:
(a) 减少单个class的大小、 (b) 把不同的功能组织到不同的category里、由多个开发者共同完成一个类、 (c) 按需加载想要的category 等等。
(2) 声明私有方法 (将class中的私有方法在category中进行声明)
Category和Extension
extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生亦随之一起消亡。
extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类添加extension。
category则完全不一样,它是在运行期决议的。extension可以添加实例变量,而category是无法添加实例变量的;extension中也可以添加属性,而category不能像extension那样添加属性、只能通过关联来实现相关的功能。
但是有一点需要特别指出:
// Category:
#import <QuartzCore/QuartzCore.h>
@interface CALayer (AveryTest)
@property (nonatomic) NSString *averyName;
@end
@implementation CALayer (AveryTest)
@dynamic averyName;
@end
// ViewController:
CALayer *layer = [[CALayer alloc] init];
NSLog(@"layer.averyName (初始值) : %@",layer.averyName);
layer.averyName = @"Avery";
NSLog(@"layer.averyName (修改后) : %@",layer.averyName);
上面的代码会Crash吗?
如果不Crash、那么就在Category中重写一下这个方法:
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
}
自己研究下是为什么。
然后再用同样的方式对UIView的Category进行一下测试看看结果会怎样。
Category剖析
先来看一下Category的结构是什么样的、下面是在runtime中的结构:
struct category_t {
const char *name; // Class的名字
classref_t cls; // 要扩展的Class
struct method_list_t *instanceMethods; // 实例方法列表
struct method_list_t *classMethods; // 类方法列表
struct protocol_list_t *protocols; // 协议列表
struct property_list_t *instanceProperties; // 属性列表
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
编译器对Category做了什么事
示例代码:
@interface Avery (Addition) <AveryProtocol>
@property (nonatomic, copy, nullable) NSString *address;
- (void)testInstanceMethod;
+ (void)testClassMethod;
@end
@implementation Avery (Addition)
- (void)testInstanceMethod {
}
+ (void)testClassMethod {
}
- (void)testProtocol {
}
- (void)setAddress:(NSString *)address {
objc_setAssociatedObject(self, @selector(address), address, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)address {
return objc_getAssociatedObject(self, _cmd);
}
@end
编译过后:
static struct _category_t _OBJC_$_CATEGORY_Avery_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Avery",
0, // &OBJC_CLASS_$_Avery,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Avery_$_Addition,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Avery_$_Addition,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Avery_$_Addition,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Avery_$_Addition,
};
这个结构体的名字是按一定的规则生成的,中间的Avery是类名,后面的Addition是Category的名字,这也就是为什么同一个类的Category名不能冲突了。
实例方法:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[4];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Avery_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
4,
{{(struct objc_selector *)"testInstanceMethod", "v16@0:8", (void *)_I_Avery_Addition_testInstanceMethod},
{(struct objc_selector *)"testProtocol", "v16@0:8", (void *)_I_Avery_Addition_testProtocol},
{(struct objc_selector *)"setAddress:", "v24@0:8@16", (void *)_I_Avery_Addition_setAddress_},
{(struct objc_selector *)"address", "@16@0:8", (void *)_I_Avery_Addition_address}}
};
类方法:
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_Avery_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"testClassMethod", "v16@0:8", (void *)_C_Avery_Addition_testClassMethod}}
};
协议方法:
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_Avery_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_AveryProtocol
};
属性列表:
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_Avery_$_Addition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"address","T@\"NSString\",C,N"}}
};
这个类的Category生成了一个数组,存在了__DATA段下的"__objc_catlist" section里:
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_Avery_$_Addition,
};
那么Runtime又是怎么加载Category的
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);
}
我们找到runtime的加载入口_objc_init方法,在library加载前由libSystem dyld调用,进行初始化操作;
首先调用map_images方法将文件中的image map到内存对其二进制内容进行解析,初始化里面的类的结构等。map_images中调用_read_images、_read_images方法初始化map后的image,这里面干了很多的事情,比如load所有的类、协议和category;然后调用load_images进行初始化操作。
_objc_init中的map_images方法
Class的初始化入口:(map_images -> map_images_nolock -> _read_images)
// _read_images函数中主要的方法:
if (!doneOnce) { // Disable nonpointer isa
gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
for (EACH_HEADER) { // Discover classes
classref_t *classlist = _getObjc2ClassList(hi, &count);
}
for (EACH_HEADER) { // Fix up remapped classes
Class *classrefs = _getObjc2ClassRefs(hi, &count);
}
for (EACH_HEADER) { // Fix up selector references
SEL *sels = _getObjc2SelectorRefs(hi, &count);
}
for (EACH_HEADER) { // Fix up old objc_msgSend_fixup call sites
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
}
for (EACH_HEADER) { // Discover protocols.
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
}
for (EACH_HEADER) { // Fix up protocol references
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
}
for (EACH_HEADER) { // Realize non-lazy classes (for +load methods and static instances)
classref_t *classlist = _getObjc2NonlazyClassList(hi, &count);
}
realizeClass(); // 解析classes
for (EACH_HEADER) { // Discover categories.
category_t **catlist = _getObjc2CategoryList(hi, &count);
}
// 以上的操作都是获取编译后的 section 中数据的方法 (把对应 section 里的数据取出来然后再进行加工、最后添加到缓存中):
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");
"__objc_classlist": 项目中全部类列表、对应全局变量 "gdb_objc_realized_classes"
"__objc_classrefs": 项目中被引用的类列表对应全局变量 "noClassesRemapped"
Class的初始化入口
classref_t *classlist = _getObjc2ClassList(hi, &count);
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
realizeClass(resolvedFutureClasses[i]);
resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}
realizeClass方法的实现如下(简化版):
/***********************************************************************
* realizeClass 实现类
* Performs first-time initialization on class cls, 在类cls上执行首次初始化
* including allocating its read-write data. 包括分配读写数据
* Returns the real class structure for the class. 返回类的实际类结构
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClass(Class cls)
{
if (cls->isRealized()) return cls;
ro = (const class_ro_t *)cls->data(); // 类的cls->data()函数得到的直接就是class_ro_t
if (ro->flags & RO_FUTURE) { // 如果rw已经分配了内存
// This was a future class. rw data is already allocated.
rw = cls->data(); // 将rw指向cls->data()
ro = cls->data()->ro; // 将初始的ro的指针指向rw中的ro
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else { // 如果rw还没有分配内存
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro; // 将rw的ro指针指向初始的ro
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw); // 调整类的data()
}
isMeta = ro->flags & RO_META;
// Attach categories
methodizeClass(cls);
return cls;
}
最开始的时候、在初始化之前Class是没有读写(rw)部分的,只有只读(ro)部分,即ro = (const class_ro_t *)cls->data();
初始化的时候才会分配空间给读写信息(即rw),然后通过"rw->ro = ro;"将class_rw_t自身的ro指针指向真正的ro信息。
再通过"cls->setData(rw);"对cls->data()进行修改。
当有了class_rw_t之后,才会去进行category的处理: 将Class本身的方法列表和Category里面的方法列表先后放到class_rw_t的method_array_t methods里面,Class自身的方法列表会被最先放入其中,并且置于列表的尾部,Category方法列表的加入顺序等同与category文件参与编译的顺序。
realizeClass方法中调用methodizeClass方法将方法列表、属性列表以及协议列表存放到rw中。
methodizeClass方法的主要作用:Fixes up cls's method list, protocol list, and property list;Attaches any outstanding categories。
methodizeClass的实现如下(简化版):
/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls)
{
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
}
当methodizeClass方法将方法列表、属性列表以及协议列表存放到rw中后调用unattachedCategoriesForClass方法并获得其返回值categories、调用attachCategories来追加调用unattachedCategoriesForClass方法返回的categories:
/***********************************************************************
* unattachedCategoriesForClass
* Returns the list of unattached categories for a class, and
* deletes them from the list.
* The result must be freed by the caller.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static category_list *
unattachedCategoriesForClass(Class cls, bool realizing)
{
runtimeLock.assertLocked();
return (category_list *)NXMapRemove(unattachedCategories(), cls);
}
attachCategories的实现如下(简化版):
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
// 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;
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
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;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
这个方法把一个类所有的category_list的所有方法取出来组成一个method_list_t ** mlists,注意这里是倒序添加的,也就是说新生成的category的方法会先于旧的category的方法插入到mlists中。然后通过methods.attachLists方法将这个二维数组mlists添加到rw中。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]));
}
}
如上代码中、attachLists在操作addedLists的数据时会现将原先的数据往后挪动addedCount位然后再将addedLists添加到数组的前部。
也就是说如果原来类中包含有方法m_a, m_b, m_c,Category中包含方法c_1, c_2, c_3,那么插入之后的方法将会是c_1, c_2, c_3, m_a, m_b, m_c。
注意realizeClass方法中下面的这个判断
bool isRealized() {
return data()->flags & RW_REALIZED;
}
// Values for class_rw_t->flags
// These are not emitted by the compiler and are never used in class_ro_t.
// Their presence should be considered in future ABI versions.
// class_t->data is class_rw_t, not class_ro_t
#define RW_REALIZED (1<<31)
即如果已经初始化那么就会返回当前的cls、realizeClass方法就不会再去调用" methodizeClass -> attachCategories"等方法。
那么在这种情况下是怎么加载category的呢?
map_images -> map_images_nolock -> _read_images ->
_getObjc2ClassList & _getObjc2ProtocolList & _getObjc2NonlazyClassList & _getObjc2CategoryList
category的初始化方法中循环调用了_getObjc2CategoryList方法 (获取"__objc_catlist"中或得到的categories):
category_t **catlist = _getObjc2CategoryList(hi, &count);
GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist");
"__objc_catlist"就是上面编译后category存放的数据段了,在调用完_getObjc2CategoryList后,runtime便开始了对category的处理:
// 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);
// 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); // Records an unattached category of cls.
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
}
if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi); // Records an unattached category of cls.
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
}
总共分成两部分来进行处理:
首先是处理category中的实例方法、通过addUnattachedCategoryForClass(cat, cls, hi)实现;
然后是处理category中的类方法、通过addUnattachedCategoryForClass(cat, cls->ISA(), hi)实现;
如果Class已初始化那么则会在remethodizeClass方法中调用attachCategories方法去追加所有的Category中的方法到rw的方法列表中去。
remethodizeClass方法的主要功能:
Attach outstanding categories to an existing class;
Fixes up cls's method list, protocol list, and property list;
Updates method caches for cls and its subclasses.
remethodizeClass方法的实现如下:
/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
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*/))) {
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
_objc_init中的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);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
load_images方法的实现共分为3个部分:
(1) 判断是否有Load方法,没有直接返回; (hasLoadMethods)
(2) 查找所有的Load方法; (prepare_load_methods)
(3) 调用所有Load方法; (call_load_methods)
判断是否有Load方法
bool hasLoadMethods(const headerType *mhdr)
{
size_t count;
if (_getObjc2NonlazyClassList(mhdr, &count) && count > 0) return true;
if (_getObjc2NonlazyCategoryList(mhdr, &count) && count > 0) return true;
return false;
}
GETSECT(_getObjc2NonlazyClassList, classref_t, "__objc_nlclslist");
GETSECT(_getObjc2NonlazyCategoryList, category_t *, "__objc_nlcatlist");
_getObjc2NonlazyClassList和_getObjc2NonlazyCategoryList方法都是最终调用getDataSection方法,这个方法是从镜像文件中读取响应的代码段,判断具体的是否有load方法,__objc_nlclslist标识的是类 +load 函数列表,__objc_nlcatlist标识的是分类中+load 函数列表。
template <typename T>
T* getDataSection(const headerType *mhdr, const char *sectname,
size_t *outBytes, size_t *outCount)
{
unsigned long byteCount = 0;
T* data = (T*)getsectiondata(mhdr, "__DATA", sectname, &byteCount);
if (!data) {
data = (T*)getsectiondata(mhdr, "__DATA_CONST", sectname, &byteCount);
}
if (!data) {
data = (T*)getsectiondata(mhdr, "__DATA_DIRTY", sectname, &byteCount);
}
if (outBytes) *outBytes = byteCount;
if (outCount) *outCount = byteCount / sizeof(T);
return data;
}
#define GETSECT(name, type, sectname) \
type *name(const headerType *mhdr, size_t *outCount) { \
return getDataSection<type>(mhdr, sectname, nil, outCount); \
} \
type *name(const header_info *hi, size_t *outCount) { \
return getDataSection<type>(hi->mhdr(), sectname, nil, outCount); \
}
查找所有的Load方法
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++) {
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());
add_category_to_loadable_list(cat);
}
}
查找所有的load方法共分2步:
(1) 获取所有类的Load方法,添加到列表(loadable_classes)中。
(2) 获取Category类的Load方法,添加到列表(loadable_categories)中。
获取所有类的Load方法并添加到列表 (loadable_classes) 中
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
schedule_class_load中主要做了2件事情:判断类中的load方法是否添加过;递归调用父类,保证父类先于子类通过add_class_to_loadable_list添加到loadable_classes中。
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
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));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
获取类的load方法的IMP:
IMP
objc_class::getLoadMethod()
{
runtimeLock.assertLocked();
const method_list_t *mlist;
assert(isRealized());
assert(ISA()->isRealized());
assert(!isMetaClass());
assert(ISA()->isMetaClass());
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;
}
获取Category中的Load方法并添加到列表 (loadable_categories)中
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
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));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
调用所有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) {
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;
}
从上面的代码中可看出"call_class_loads();" 先于 "call_category_loads();"调用、即类的load方法先于Category中的load方法。另外也可以看出load方法中的代码在执行时被autoreleasePool包含了。
父类的load方法会先于子类调用、如何实现的 ?
call_load_methods -> call_class_loads
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;
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);
}
从call_class_loads的代码中可以看出call_class_loads通过获取loadable_classes表中的数据来进行顺序调用的,所以类和父类的调用顺序与loadable_classes创建时的添加顺序有关。
prepare_load_methods -> schedule_class_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;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
从上面代码可以看出,当cls有父类时、即cls->superclass不为空时就会先添加父类Load方法到loadable_classes中。
所以在loadable_classes列表中父类会位于子类的前面、所以当调用call_class_loads方法时父类的load会先于子类被调用。
如果category的镜像先于类的镜像加载,那么怎么保证类先于分类加载呢 ?
call_load_methods -> call_category_loads
static bool call_category_loads(void)
{
........
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
........
}
从上面的call_category_loads代码可以看出,在加载Category的load方法时,会判断类的load方法是否已经调用, 从而保证类优先于分类调用。
Category中的方法是如何"覆盖"原始方法的?
方法查找的流程: lookUpImpOrForward -> getMethodNoSuper_nolock
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
List** beginLists() {
if (hasArray()) {
return array()->lists;
} else {
return &list;
}
}
List** endLists() {
if (hasArray()) {
return array()->lists + array()->count;
} else if (list) {
return &list + 1;
} else {
return &list;
}
}
方法的查找是从array()->lists开始顺序查找的、一旦找到了就会return。因此 category 中定义的同名方法不会替换类中原有的方法,但是对原方法的调用实际上会调用 category 中的方法。
在类的load方法中可以调用在Category中声明的方法吗?
可以调用,因为附加category到类的工作会先于+load方法的执行。
_objc_init -> map_images的时候会将category中的方法列表、协议列表等添加到cls的rw中去;
+load方法的调用是在_objc_init -> load_images中进行的。
最后来张图来看下load方法的调用栈:
【 请勿直接转载 - 节约能源从你我做起 】