iOS - initialize 方法探索

[toc]

参考

initialize

load

code

// NSObject 有实现该方法
+ (void)initialize;

objc4源码解析

思路

既然 +initialize 方法会在类第一次接收到消息时被调用,

所以 objc_msgSend()方法内部必然有调用 +initialize

但是 objc_msgSend() 的C源码没有开源, 只提供了汇编代码。

我们只能通过其寻找方法的链路思考:

isa -> 类对象/元类对象, 查找方法, 调用; 若找不到, 查找父类 ↓

super_class -> 类对象/元类对象, 查找方法, 调用; 若找不到, 继续查找父类 ↓

...

猜想, 在查找方法这一步, 有可能调用了 +initialize

源码(部分)
// 获取类方法
Method class_getClassMethod(Class cls, SEL sel) {
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
}

Method class_getInstanceMethod(Class cls, SEL sel) {
    if (!cls  ||  !sel) return nil;
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER); // 查找IMP
    return _class_getMethod(cls, sel);
}
// 查找方法
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {
    // 未被初始化, 就调用
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock); // 
    }
    // ...
}

static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock) {
    return initializeAndMaybeRelock(cls, obj, lock, true);
}

static Class initializeAndMaybeRelock(Class cls, id inst, mutex_t& lock, bool leaveLocked) {
    // ...
    Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
    initializeNonMetaClass(nonmeta);
}

void initializeNonMetaClass(Class cls) {
    supercls = cls->superclass;
    // 有父类, 且父类未初始化, 递归调用, 先初始化父类
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    callInitialize(cls);
    // ... 
}

// objc-initialize.mm
void callInitialize(Class cls) {
    // 调用了 initialize
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}
结论
调用方式

由系统自动调用, 不能手动调用

系统通过消息发送机制, objc_msgSend()

调用顺序

先调用父类的 +initialize, 再调用子类的 +initialize

(如果子没实现就调用父类的, 根类NSObject有实现, 保证了不会报错)

太乱不看
父类 / 分类

+initialize 是通过 objc_msgSend() 进行调用的, 所以有以下特点:

  1. initialize 遵循继承规则, 父类的 initialize 会比子类先执行;

    如果被发消息的类及其分类 都没有实现 initialize, 则会查找并调用其父类的 initialize, 这种情况父类的initialize会被调用多次。

  2. 分类覆盖主类, 如果被发消息的类的分类实现了+initialize 方法, 就会覆盖这个类中的实现。

调用时机

在main()函数之后, 在该类或其子类收到第一条消息之前;
当向该类发送的第一个消息, 一般是类消息首先调用, 常见的是alloc;

《Apple Document》
Initializes the class before it receives its first message.
The runtime sends initialize to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program. Superclasses receive this message before their subclasses.

调用次数

结论:

不一定只被调用一次;

若子类及其分类未实现 initialize, 父类的 initialize 会被调用多次;

但父类的initialize被调用多次, 并不代表父类被初始化多次, 仅仅是子类调用了父类的方法而已。

分析:

initialize 的调用与普通方法一样都是使用 runtime 的消息发送机制。

  1. 若第一次给某个子类发送消息, 初始化该子类前需先初始化其父类, 此时父类的initialize被调用1次; (若此前父类已被初始化, 则不会再调用, 但也已经被调用一次)

    消息机制会通过子类的isa找到子类的元类, 查找该元类的方法列表, 如果子类没有实现initialize, 会通过元类的super_class指针查找父元类, 然后查找并调用父元类的initialize, 导致父元类的initialize被调用两次; (类方法存储在元类的方法列表)

    而如果子类没有实现initialize, 会查找并调用父类的initialize, 导致父类的initialize被调用两次;

    所以通常要在方法内加一个判断, 确认当前要初始化的是不是本类, 防止因子类的调用而将原本只需执行一次的代码执行两次。

  1. 另外, 初始化子类前, 系统会自动初始化父类, 子类不要手动调用super, 否则父类的initialize会被调用多次。

《Apple Document》
The superclass implementation may be called multiple times if subclasses do not implement initialize—the runtime will call the inherited implementation—or if subclasses explicitly call [super initialize]. If you want to protect yourself from being run multiple times, you can structure your implementation along these lines: Listing 1

+ (void)initialize {
    if (self == [ClassName self]) {
        // ... do the initialization ...
    }
}

调用必然性

不一定被调用;

initialize 是懒加载的, 如果程序一直没有给某个类或它的子类发送消息, 那么它永远不会被调用, 这一点有利于节省系统资源, 避免浪费。


应用场景

苹果提供 initialize方法, 就是给开发者使用的, 第一次使用这个类的时候做一些事情。

initialize方法一般用于初始化全局变量 或 静态变量。

无法在编译期设定的全局变量, 可以放在initialize方法中初始化。

安全性

线程安全 线程阻塞

运行期系统会确保initialize方法是在线程安全的环境中执行, 即只有执行initialize的那个线程可以操作类或类实例。其他线程都要先阻塞, 等待initialize执行完

《Apple Document》
The runtime sends the initialize message to classes in a thread-safe manner. That is, initialize is run by the first thread to send a message to a class, and any other thread that tries to send a message to that class will block until initialize completes.
Because initialize is called in a blocking manner, it’s important to limit method implementations to the minimum amount of work necessary possible. Specifically, any code that takes locks that might be required by other classes in their initialize methods is liable to lead to deadlocks. Therefore, you should not rely on initialize for complex initialization, and should instead limit it to straightforward, class local initialization.

面试题

和 init 的区别?
+ (void)initialize;  // 类方法, 每个类第一次被初始化的时候调用一次; (alloc)
- (instancetype)init;  // 对象方法, 每个对象初始化的时候调用一次;

initialize 的调用在 init 之前。

和 load 的区别

见【load - 面试题】

什么情况下initialize会被调用多次?

【见本文 - 调用次数】

initialize 初始化类指的是创建类对象?

应该是 [TBC]

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

推荐阅读更多精彩内容