iOS原理篇(三): 关于Category

  • 什么是类别(Category
  • Category的使用场合
  • Category实现原理
  • +load()+initialize()
  • 总结

一、什么是类别

类别(Category)是Objective-C中一个灵活的类扩展机制,用于在不获悉、不改变原来代码的情况下往一个已经存在的类中添加新的方法,Category扩展的新方法有更高的优先级,会覆盖类中同名的已有方法。类别的设计体现了面向对象的核心原则,即开放封闭原则(Open Closed PrincipleOCP)。对扩展开放,对修改封闭,从而降低代码的耦合度。

二、Category的使用场合

  • 将类的实现分散到多个不同文件或多个不同框架中(为已有的类扩充新的方法
  • 创建对私有方法的前向引用
  • 可以向对象添加非正式协议

类别(Category)和 类扩展(Extension)的区别:

  • 类扩展可以添加属性,类扩展添加的方法是必须要实现的,类扩展可以认为是一个私有的匿名类别,因为类扩展定义在.m文件头部,添加的属性和方法都没有暴露在头文件,所以在不考虑运行时特性的前提下,这些扩展属性和方法只能在类内部使用,一定程度上可以说是实现了私有的机制;
  • 类扩展(Class Extension)在编译的时候,它的数据就已经包含在类信息中,而类别(Category)是在运行时,才会将数据合并到类信息中;

三、Category实现原理

Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息,在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中);

下面代码为DJTPerson添加了两个分类,之后分析都基于这段代码

//DJTPerson.h
@interface DJTPerson : NSObject
{
    int _age;
}
- (void)run;
@end

//DJTPerson.m
@implementation DJTPerson
- (void)run
{
    NSLog(@"run");
}
@end
//分类1
//DJTPerson+Test.h
@interface DJTPerson (Test)<NSCoding>
@property (assign, nonatomic) int age;

- (void)test;
+ (void)abc;

-(void)setAge:(int)age;
-(int)age;
@end

//DJTPerson+Test.m
@implementation DJTPerson (Test)
- (void)test
{
    NSLog(@"test");
}

+ (void)abc
{
    NSLog(@"abc");
}
- (void)setAge:(int)age
{
}
- (int)age
{
    return 10;
}
@end
//分类2
//DJTPerson+Test2.h
@interface DJTPerson (Test2)
@end

//DJTPerson+Test2.m"
@implementation DJTPerson (Test2)
- (void)run
{
    NSLog(@"DJTPerson (Test2) - run");
}
@end

我们知道实例对象的 isa指针指向类对象,类对象的isa指针指向元类对象,当person对象调用run方法时,通过isa指针找到类对象,然后在类对象的对象方法列表中查找方法,如果没找到就通过类对象的superclass指针找到父类对象,接着寻找run方法。
那么调用分类的方法时,是否按照同样的步骤呢?其实,OC中处理分类需要两步:

  • 分类经过编译,会生成_category_t这样的一个结构体,分类中的数据都会存储在这个结构体中,多少个分类对应对应多少个这样的结构体;
  • 编译完以后,通过runtime动态将生成的结构体中存放的分类数据合并到类对象、元类对象中。

为了验证以上描述,首先通过clang编译器将分类转换为C++,查看分类编译后结构:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc DJTPerson+Test.m
在生成的C++文件中,会生成一个_category_t的结构体

struct _category_t {
    const char *name;//所属类名,本文为DJTPerson
    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_t结构体中,存放着类名name、对象方法列表instanceMethods、类方法列表classMethods、协议列表protocols和属性列表classProperties;并没有存放成员变量的列表,这也说明了分类中是不允许添加成员变量的,分类中添加的属性也不会帮助我们自动生成成员变量,只会生成getset方法的声明,需要我们自己去实现;
接着,在文件中我们可以看到_method_list_t类型的结构体:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    3,
    {{(struct objc_selector *)"test", "v16@0:8", (void *)_I_DJTPerson_Test_test},
    {(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_DJTPerson_Test_setAge_},
    {(struct objc_selector *)"age", "i16@0:8", (void *)_I_DJTPerson_Test_age}}
};

_OBJC_$_CATEGORY_INSTANCE_METHODS_DJTPerson_$_Test这个结构体名字看出它是 INSTANCE_METHODS对象方法,并且为这个结构体赋值,结构体中存储了方法占用的内存、方法数量和方法列表。并且找到分类中我们实现的对象方法:testsetAgeage

同样的,可以看到C++文件中还包含了_method_list_t类型的类方法结构体:

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_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"abc", "v16@0:8", (void *)_C_DJTPerson_Test_abc}}
};

从名字也看出_OBJC_$_CATEGORY_CLASS_METHODS_DJTPerson_$_Test这个就是类方法列表结构体;

接下来是协议方法列表:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCoding __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"encodeWithCoder:", "v24@0:8@16", 0},
    {(struct objc_selector *)"initWithCoder:", "@24@0:8@16", 0}}
};

struct _protocol_t _OBJC_PROTOCOL_NSCoding __attribute__ ((used)) = {
    0,
    "NSCoding",
    0,
    (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCoding,
    0,
    0,
    0,
    0,
    sizeof(_protocol_t),
    0,
    (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCoding
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCoding = &_OBJC_PROTOCOL_NSCoding;

static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_NSCoding
};

属性列表:

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_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"age","Ti,N"}}
};

最后在_OBJC_$_CATEGORY_DJTPerson_$_Test结构体中对上面的结构体一一赋值:

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_DJTPerson;
static struct _category_t _OBJC_$_CATEGORY_DJTPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "DJTPerson",
    0, // &OBJC_CLASS_$_DJTPerson,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_DJTPerson_$_Test,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_DJTPerson_$_Test,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_DJTPerson_$_Test,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_DJTPerson_$_Test,
};
static void OBJC_CATEGORY_SETUP_$_DJTPerson_$_Test(void ) {
    _OBJC_$_CATEGORY_DJTPerson_$_Test.cls = &OBJC_CLASS_$_DJTPerson;
}

最后将_OBJC_$_CATEGORY_DJTPreson_$_Testcls指针指向_class_t类型的OBJC_CLASS_$_DJTPreson结构体地址,所以cls指向的应该是分类的主类类对象的地址

通过以上分析,分类编译后确实将我们定义的对象方法,类方法,属性等都存放在_catagory_t结构体中,接下来分析runtime源码,查看_catagory_t中存储的方法、属性、协议等是如何存储在类对象中的。

首先来到runtime初始化函数

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

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);
}

&map_images读取模块(images这里代表模块)返回的map_images_nolock函数中找到_read_images函数,在_read_images函数中我们找到分类相关代码:

 // 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).
                // 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);
                }
            }
        }
    }

这段代码用来查找有没有分类,通过_getObjc2CategoryList函数获取到分类列表进行遍历,来获取其中的方法、协议和属性等,而最终都调用了remethodizeClass(cls)函数,我们查看remethodizeClass(cls)函数:

/***********************************************************************
* 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.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_t保存在category_list中。再看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)
{
    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;
    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);
}

从源码看出,首先根据方法列表、属性列表和协议列表malloc分配内存,根据多少个分类以及每一块方法需要多少内存来分配相应的内存地址。遍历分类方法、属性以及协议放入对应 mlistproplistsprotolosts数组中。
然后通过类对象的data()方法,拿到类对象的class_rw_t结构体rw,而class_rw_t中存放着类对象的方法、属性和协议等数据,rw结构体通过类对象的data方法获取,所以rw里面存放这类对象里面的数据。
接着分别通过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]));
        }
    }

上述源代码中,array()->lists数组代表类对象原来的方法列表、属性列表和协议列表, addedLists传入所有分类的方法列表、属性列表和协议列表;attachLists函数中最重要的两个方法为memmove内存移动和memcpy内存拷贝,memmove能保证以前的数据能完整的挪动到指定位置;
经过memmovememcpy方法之后,分类的方法、属性和协议列表被放在了类对象中原本存储的方法、属性和协议列表前面。
那为什么要将分类方法列表追加到本来的对象方法前面呢,这是为了保证分类方法优先调用,我们知道当分类重写本类的方法时,会覆盖本类的方法;其实经过上面的分析我们知道本质上并不是覆盖,而是优先调用,本类的方法依然在内存中,可以通过打印类的所有方法进行查看,再DJTPerson类中存储着两个run方法;

- (void)printMethodNamesOfClass:(Class)cls
{
    unsigned int count;
    // 获得方法数组
    Method *methodList = class_copyMethodList(cls, &count);
    // 存储方法名
    NSMutableString *methodNames = [NSMutableString string];
    // 遍历所有的方法
    for (int i = 0; i < count; i++) {
        // 获得方法
        Method method = methodList[i];
        // 获得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    // 释放
    free(methodList);
    // 打印方法名
    NSLog(@"%@ - %@", cls, methodNames);
}

- (void)viewDidLoad {
    [super viewDidLoad];    
    DJTPreson *p = [[DJTPreson alloc] init];
    [p run];
    [self printMethodNamesOfClass:[DJTPreson class]];
}

打印结果:

2019-05-04[49082:260754984] DJTPerson (Test2) - run
2019-05-04[49082:260754984] DJTPerson - test, run, run, setAge:, setAge:, age, age,

四、+load()+initialize()

+load方法调用原理

+load方法是在runtime加载类、分类的时候调用,每个类、分类的+load方法,在程序运行过程中只调用一次;他和其他方法的调用不同,不是通过消息机制(isa一层层查找),而是在类、分类加载时直接通过函数地址调用。

下面示例代码中,定义DJTPerson类和它的两个分类Test1Test2:

// DJTPerson
@interface DJTPerson : NSObject
@end

@implementation DJTPerson
+ (void)load
{
    NSLog(@"DJTPerson +load");
}
@end

//分类DJTPerson+Test1
@interface DJTPerson (Test1)
@end

@implementation DJTPerson (Test1)
+ (void)load
{
    NSLog(@"DJTPerson(Test1) +load");
}
@end

//分类DJTPerson+Test2
@interface DJTPerson (Test2)
@end

@implementation DJTPerson (Test2)
+ (void)load
{
    NSLog(@"DJTPerson(Test2) +load");
}
@end

直接运行程序,即使没有在main函数中引用上述头文件,依然有如下打印:


这说明虽然并没有使用到这些类,但程序运行这些类会被载进内存,就会调用它们的 +load方法;这和前面所说的在分类中定义同名的run方法会覆盖原类的run方法不同,明明分类中有定义load方法,覆盖了原类中的load方法,那为什么DJTPerson中的load方法还会被调用呢?(这里说覆盖并不准确,而是分类中同名方法经合并会放在方法列表的前面);
通过阅读运行时源码,在运行是入口找到load_images方法:

load_images方法内部会调用call_load_methods(void)方法

从源码看出,先调用类的load方法,在调用分类的load方法,这和编译顺序无关,如下调换编译顺序,依然先调用 DJTPersonload方法:


那类的load方法又是如何调用呢?进入call_class_loads()方法:


这里调用类的load方法是直接通过函数指针进行调用,不会向之前调用run方法那样去方法列表查找,或者说不是通过消息发送机制的形式。


分类的load方法是如何调用呢?进入call_category_loads(void)方法:


我们发现它也是通过指针指向分类load方法地址,然后直接调用;
总结:从上面看出确实是优先调用类的load方法,然后调用分类的load方法;

接着,考虑有多个类,一个类有多个分类,以及类存在父类的情况,我们看看此时+load方法的调用顺序,新增DJTDogDJTCat两个类继承自NSObject,新增DJTStudent类继承自DJTPerson,同时为DJTStudent添加两个分类Test1Test2,编译顺序如下:


从打印结果看出:

  • 一定是优先调用类的+load方法,然后调用分类+load方法,这个和编译顺序无关;
  • 类与类之间+load方法调用顺序和编译顺序有关,比如DJTDogDJTCatDJTStudent之前调用+load方法;
  • 在调用子类的+load方法之前,会保证其父类的+load方法已经调用,即父类的+load方法优先调用,即使父类编译顺序在子类后面,比如DJTPersonDJTStudent
  • 分类的+load方法调用顺序和编译顺序有关,先编译先调用。

+initialize方法调用原理

+initialize方法会在类第一次接收到消息时调用,并且它的调用顺序是先调用父类的+initialize,再调用子类的+initialize,前提是父类的+initialize方法没有被调用过。

查看源码,在objc-class.mm文件中,有一个获取对象方法的函数class_getInstanceMethod()和一个获取类方法的函数class_getClassMethod(),在class_getClassMethod()函数中其实也是调用的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(),再调用lookUpImpOrForward():

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    ...............

    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
    }
    ...................

上面if中判断传入的参数initialize,表示是否需要初始化,cls->isInitialized()判断这个类是否已经初始化,在满足需要初始化并且类未初始化时,调用_class_initialize()方法进行初始化,我们看一下_class_initialize()中都做了什么:

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) {
     .............
     @try
        {
            callInitialize(cls);
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
        }
    else if (cls->isInitialized()) {
        // Set CLS_INITIALIZING failed because someone else already 
        return;
    }
    else {
        // We shouldn't be here. 
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

if判断中,如果存在父类,并且父类没有初始化,先去初始化父类,即调用callInitialize(cls)方法:

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

从上面源码看出,+initialize()方法最终是通过objc_msgSend消息发送机制调用的,消息发送机制通过isa指针找到对应的方法与实现,因此如果分类方法中有实现,会优先调用分类方法中的实现;

综上,我们为DJTPresonDJTStudentDJTStudent+Test 添加initialize方法,当类第一次接收到消息时(或者说第一次使用类的时),就会调用initialize,在调用子类的initialize之前,会先保证调用父类的initialize方法,如果之前已经调用过initialize,就不会再调用initialize方法了,当分类重写initialize方法时会先调用分类的方法。

五、总结

  • Catagory中有load方法吗?load方法是什么时候调用的?load方法能继承吗?
    Category中有load方法,load方法在程序启动装载类信息的时候就会调用,load方法可以继承,但一半不会手动去调用,调用子类的load方法之前,会先调用父类的load方法。

  • loadinitialize方法的区别是什么?
    调用方式load是根据函数地址直接调用,initialize是通过objc_msgSend调用;
    调用时刻loadruntime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次);

  • 它们在Category中调用的顺序是什么?以及出现继承时它们之间的调用过程?
    load方法:先编译哪个类,就先调用那个类的load,在调用load之前会先调用父类的load方法;分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法;
    initialize方法:先初始化父类,之后再初始化子类;如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 1.内存管理: 影响:如果app占用内存过大, 系统可能会强制关闭app, 造成闪退现象, 影响用户体验. 概念:...
    SoftKnife阅读 389评论 0 1
  • 这本书的作者是史蒂芬·柯维,相信很多人都知道他的另一本书《高效能人士的七个习惯》,这本书是他去世前写的最后一...
    乐乐_Mika阅读 9,346评论 2 6
  • 豆瓣 “一刻” 1.体验背景 机型版本 产品名称 时间 M355 Android4.4.4 一刻1.6.0...
    绮丽的夏天阅读 234评论 0 1