OC底层相关

+load VS +initialize

2019-03-28  本文已影响44人  Vency_

调用时机


+load

通过上一篇 从 MachO 加载到对象创建! 可以了解到:

+initialize

initialize 调用栈

因为方法调用会转换为消息发送, 所以会看到在发送消息的时候调用了 _objc_msgSend_uncached函数.
那么这个方法又是怎么被调起的?
注: 因为消息发送会反复被调用, 考虑性能所以苹果用汇编实现了 objc_msg 机制.
所以 :

_objc_msgSend 汇编片段 (取自 objc-msg-arm.s, 感觉好理解些, 大同小异) :

    ENTRY _objc_msgSend     // 入口

    cbz r0, LNilReceiver_f  // **获取方法调用者

    ldr r9, [r0]            // r9 = self->isa
    GetClassFromIsa         // r9 = class -- **line 307 - .macro GetClassFromIsa
    CacheLookup NORMAL      // line 251 - .macro CacheLookup **缓存查找
    // cache hit, IMP in r12, eq already set for nonstret forwarding
    bx  r12                 // call imp -- **缓存找到并执行

    // 没有找到缓存, 则会跳转到 __objc_msgSend_uncached
    CacheLookup2 NORMAL

    // cache miss
    ldr r9, [r0]             // r9 = self->isa
    GetClassFromIsa          // r9 = class
    b   __objc_msgSend_uncached // line 691 -- STATIC_ENTRY __objc_msgSend_uncached

// Receiver 为 nil
LNilReceiver:
    // r0 is already zero
    mov r1, #0
    mov r2, #0
    mov r3, #0
    FP_RETURN_ZERO  // 返回 nil
    bx  lr          // 跳转原执行入口

    END_ENTRY _objc_msgSend  // 结束
    STATIC_ENTRY __objc_msgSend_uncached

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band r9 is the class to search

    // line 649 .macro MethodTableLookup 查找方法列表
    // returns IMP in r12 
    MethodTableLookup NORMAL    
    bx  r12

    END_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup

    stmfd   sp!, {r0-r3,r7,lr}
    add r7, sp, #16
    sub sp, #8          // align stack
    FP_SAVE

.if $0 == NORMAL
    // receiver already in r0
    // selector already in r1
.else
    mov     r0, r1          // receiver
    mov     r1, r2          // selector
.endif
    mov r2, r9          // class to search

    blx __class_lookupMethodAndLoadCache3
    mov r12, r0         // r12 = IMP

.if $0 == NORMAL
    cmp r12, r12        // set eq for nonstret forwarding
.else
    tst r12, r12        // set ne for stret forwarding
.endif

    FP_RESTORE
    add sp, #8          // align stack
    ldmfd   sp!, {r0-r3,r7,lr}

.endmacro

通过函数调用栈可以看到汇编最终调用到 _class_lookupMethodAndLoadCache3 函数 (objc-msg-x86_64.s 有提示), 定义为 message dispatcher(消息调度者), 由于类也可以当做对象, 所以在这个方法中需要通过条件断点进行调试.
最终该方法中又会调用:

lookUpImpOrForward(cls, 
                   sel, 
                   obj,
                   YES/*initialize*/, 
                   NO/*cache*/, 
                   YES/*resolver*/);

该函数大致实现:

小结 :

+load 方法在程序加载时已经执行, 比 main() 函数要早.
+initialize (官方解释: Initializes the class before it receives its first message.), 显然决定 +initialize 方法调用时机是在第一次调用 cls 的方法前;

注意 : 像 NSObject (不能重载 +initialize), NSThread 这样的类在 main() 函数前就会调用其方法, 所以调用 +initialize 时机也会在 main() 函数之前, 甚至在 +load 前调用!
所以对于自定义类 +load+initialize 要早很多, 但是有些系统类的 +initialize 方法会在 +load 之前调用!

: 系统调用 +load 方法不会触发 +initialize方法, 而手动调用 +load 方法时会触发 +initialize 方法.
eg: 在 +load 方法里面调用 [super load]; 会发现在该类 +load 时会发送 [super load] 消息, 所以会先调用父类的 +initialize 方法 (手动调用, 优先查找分类) , 然后才是父类的 +load, 再然后才是当前类的 +load 方法.
如图 :

2019-03-28   +[NSThread(initialize) initialize] - DYLD 加载时调用,分类重载
2019-03-28   +[WXHuman load] - DYLD 加载时调用
2019-03-28   +[WXHuman(One) initialize] - 优先分类查找
2019-03-28   +[WXHuman(One) load] - 手动调用,优先分类查找
2019-03-28   +[WXPerson load] - [super load] 
2019-03-28   +[WXHuman(One) load] - DYLD 加载时调用
2019-03-28   +[WXPerson(One) load] - DYLD 加载时调用
2019-03-28   +[NSThread(initialize) load] - DYLD 加载时调用
2019-03-28   +[WXPerson(Initialize) load] - DYLD 加载时调用
2019-03-28   main 方法入口打印
2019-03-28   +[WXPerson(Initialize) initialize] - DYLD 加载时调用

调用


新建 WXPerson : WXHuman / WXPerson(One) / WXPerson(Two) / WXHuman(One) 类, 这些类均重写了 +load+initialize 方法:

2019-03-27 18:54:40.889322+0800 +[WXHuman load]
2019-03-27 18:54:40.889560+0800 +[WXPerson load]
2019-03-27 18:54:40.889575+0800 +[WXPerson(Initialize) load]
2019-03-27 18:54:40.889586+0800 +[WXPerson(One) load]
2019-03-27 18:54:40.889711+0800 +[WXHuman(One) initialize]
2019-03-27 18:54:40.889742+0800 +[WXPerson(One) initialize]

因为 +load 加载是遍历所有的类(只要是类)进行查找的, 所以只要实现就会调用;
+initialize 则会被分类重载方法所覆盖 (在 methodizeClass(cls) 方法实现时调用 attachList 方法时,分类添加是在类之后, 且添加 list 操作是向前添加, 所以在查找方法的时候会优先找到分类的方法), 所以只会调用分类的初始化方法, 当然可以调用 [super initialize] 调起父类 +initialize 方法.

上一篇 下一篇

猜你喜欢

热点阅读