iOS进阶回顾四「load & initialize」

load方法的本质是什么?initialize呢?两个有什么区别?

  • 最好的研究方法就是实践:新建两个类实现load方法

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson : NSObject
@end
NS_ASSUME_NONNULL_END

#import "SFPerson.h"
@implementation SFPerson
+ (void)load{
    NSLog(@"SFPerson  +load");
}
@end

#import "SFPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson (Helper)
@end
NS_ASSUME_NONNULL_END

#import "SFPerson+Helper.h"
@implementation SFPerson (Helper)
+ (void)load{
    NSLog(@"SFPerson (Helper) + load");
}
@end

#import "SFPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson (Addtion)
@end
NS_ASSUME_NONNULL_END

#import "SFPerson+Addtion.h"

@implementation SFPerson (Addtion)

+ (void)load{
    NSLog(@"SFPerson (Addtion) + load");
}

@end

2019-08-27 14:16:08.683691+0800 load[33088:238375] SFPerson +load
2019-08-27 14:16:08.684128+0800 load[33088:238375] SFPerson (Helper) + load
2019-08-27 14:38:15.283433+0800 load[33282:256018] SFPerson (Addtion) + load

直接运行后出现上面的打印输出,我们没有导入头文件,也没有进行创建对象调用,系统直接调用load方法由此可见:
分类中也存在load方法,load方法是在程序启动时,加载类、分类的时候就会调用。在调用分类的load方法前会优先调用本类的load方法。分类中的load方法谁先编译谁先调用
我们也可以通过源码验证如下:

22E093B692B25434CAC728BFA387A090.png

可以看到类的load方法调用一次就不会调用了loadable_classes_user>0这样循环就不会再次执行了
继续查看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_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()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }
    }

    // Compact detached list (order-preserving)
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;

    // Copy any new +load candidates from the new list to the detached list.
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)
                realloc(cats, allocated *
                                  sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[i];
    }

    // Destroy the new list.
    if (loadable_categories) free(loadable_categories);

    // Reattach the (now augmented) detached list. 
    // But if there's nothing left to load, destroy the list.
    if (used) {
        loadable_categories = cats;
        loadable_categories_used = used;
        loadable_categories_allocated = allocated;
    } else {
        if (cats) free(cats);
        loadable_categories = nil;
        loadable_categories_used = 0;
        loadable_categories_allocated = 0;
    }

    if (PrintLoading) {
        if (loadable_categories_used != 0) {
            _objc_inform("LOAD: %d categories still waiting for +load\n",
                         loadable_categories_used);
        }
    }

    return new_categories_added;
}
  • 我们看到无论是分类还是类都是通过找到下标直接调用的没有通过msgSend消息转发,所以load方法的本质调用为:load根据函数地址直接调用,在runtime加载类分类的时候调用,只会调用一次,先调用类的load方法再去调用分类的load,先编译的类,先调用,调用子类的load之前,先调用父类的load方法

  • 我们再添加一个SFStudent类继承SFPerson分别实现一个类方法run方法

#import "SFPerson.h"

@implementation SFPerson

+ (void)load{
    NSLog(@"SFPerson  +load");
}
+(void)run{
    NSLog(@"SFPerson  +run");

}
@end

利用[SFStudent run];进行调用输入如下:

2019-08-27 14:45:34.859386+0800 load[33449:263656] SFPerson +load
2019-08-27 14:45:34.859677+0800 load[33449:263656] SFPerson (Helper) + load
2019-08-27 14:45:34.859693+0800 load[33449:263656] SFPerson (Addtion) + load
2019-08-27 14:45:34.859769+0800 load[33449:263656] SFPerson (Addtion) +run

由此可知:分类中重写类方法时,分类的类方法会优先调用。我们利用SFStudent调用run方法时,输出为SFPerson (Addtion) +run优先调用的原因

下面继续initialize分析:

  • 我们在SFPerson中添加+(void)initialize方法
#import "SFPerson.h"

@implementation SFPerson

+(void)initialize{
    NSLog(@" SFPerson initialize ");
}
@end

main.m中调用如下

#import <Foundation/Foundation.h>
#import "SFStudent.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [SFStudent run];
    }
    return 0;
}

2019-08-27 15:39:29.802479+0800 load[39941:335527] SFPerson initialize
2019-08-27 15:39:29.802491+0800 load[39941:335527] SFPerson initialize

  • 我们看到调用了两次initialize方法,为什么呢?
#import <Foundation/Foundation.h>
#import "SFStudent.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [SFStudent run];
        [SFStudent run];
        [SFStudent run];

    }
    return 0;
}

2019-08-27 16:20:53.860266+0800 load[40134:360148] SFPerson initialize
2019-08-27 16:20:53.860282+0800 load[40134:360148] SFPerson initialize

  • 多次调用的输入结果和上面的一样
    initialize方法会在类第一次接收消息的时候调用 调用顺序:先调用父类的+initialize,在调用子类的+initialize无论多少次调用都执行一次,是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
    查看源码:
    9FF98C1983E5B2CA36457C94BE87F727.png

    查看源码可以看到initialize方法是通过消息转发机制实现的
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

调用时间:

  • load方法是可以继承的,但是一般情况下,都是不会主动调用load方法,都是让系统自动调用
  • + initialize方法会在类第一次接收消息的时候调用
    调用顺序:
  • 先调用父类的+initialize,在调用子类+initialize父类的初始化方法可能调用多次, 先初始化父类再初始化子类,而且每个类只会初始化一次, 如果分类实现了+initialize,就覆盖类本身的+initialize方法
  • 先调用类的load方法再去调用分类的load,先编译的类,先调用,调用子类的load之前,先调用父类的load
    调用方式的:
  • +initialize是通过objc_msgSend进行调用的
  • load根据函数地址直接调用,在runtime加载类分类的时候调用,只会调用一次
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,188评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,464评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,562评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,893评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,917评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,708评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,430评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,342评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,801评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,976评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,115评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,804评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,458评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,008评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,135评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,365评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,055评论 2 355

推荐阅读更多精彩内容