关于initialize的调用时机

initialize翻阅资料显示是 :会在类第一次收到消息的时候调用

1.我们知道 , 不管是调用类方法, 还是对象方法, 本质就是利用objc_msgSend消息机制给对象/类对象发送消息 ,通过isa指针去类对象 , 或者元类对象中寻找方法

2.回到initialize , 既然是第一次收到消息的时候会调用initialize ,最简单的办法就是去看底层实现 , 基于objc4-781

3.源码截图:

image.png

全是汇编啊 ,问题不大 ,对objc_msgSend调试

4.实验例子

image.png

教大家一些小技巧
1.Xcode -> Debug ->Debug Workflow->Always Show Disassembly 显示汇编
2.对[Person alloc] 下断点 , 然后控制台 b objc_msgSend 对objc_msgSend 函数下断点
3. 控制台 c :是跳转到下一个断点 控制台:ni:单步调 控制台:si:单步调,遇到方法调用就进入方法

4.汇编里面的函数跳转, 都是跟b有关的, cbz, bl, b.ne ,b , 看到这些就可以下个断点 ,无外乎就是有没有返回值, 是否比较之类的,但总是要跳转的

5.对objc_msgSend下断点

image.png

objc_msgSend的汇编实现 , 也可以对照objc4-781中的objc_msgSend的汇编

libobjc.A.dylib`objc_msgSend:
->  0x189b6c9a0 <+0>:   cmp    x0, #0x0                  ; =0x0 
   0x189b6c9a4 <+4>:   b.le   0x189b6ca20               ; <+128>
   0x189b6c9a8 <+8>:   ldr    x13, [x0]
   0x189b6c9ac <+12>:  and    x16, x13, #0xffffffff8
   0x189b6c9b0 <+16>:  ldr    x11, [x16, #0x10]
   0x189b6c9b4 <+20>:  and    x10, x11, #0xffffffffffff
   0x189b6c9b8 <+24>:  and    x12, x1, x11, lsr #48
   0x189b6c9bc <+28>:  add    x12, x10, x12, lsl #4
   0x189b6c9c0 <+32>:  ldp    x17, x9, [x12]
   0x189b6c9c4 <+36>:  cmp    x9, x1
   0x189b6c9c8 <+40>:  b.ne   0x189b6c9d8               ; <+56>
   0x189b6c9cc <+44>:  eor    x12, x12, x1
   0x189b6c9d0 <+48>:  eor    x12, x12, x16
   0x189b6c9d4 <+52>:  brab   x17, x12
   0x189b6c9d8 <+56>:  cbz    x9, 0x189b6cd20           ; _objc_msgSend_uncached
   0x189b6c9dc <+60>:  cmp    x12, x10
   0x189b6c9e0 <+64>:  b.eq   0x189b6c9ec               ; <+76>
   0x189b6c9e4 <+68>:  ldp    x17, x9, [x12, #-0x10]!
   0x189b6c9e8 <+72>:  b      0x189b6c9c4               ; <+36>
   0x189b6c9ec <+76>:  add    x12, x12, x11, lsr #44
   0x189b6c9f0 <+80>:  ldp    x17, x9, [x12]
   0x189b6c9f4 <+84>:  cmp    x9, x1
   0x189b6c9f8 <+88>:  b.ne   0x189b6ca08               ; <+104>
   0x189b6c9fc <+92>:  eor    x12, x12, x1
   0x189b6ca00 <+96>:  eor    x12, x12, x16
   0x189b6ca04 <+100>: brab   x17, x12
   0x189b6ca08 <+104>: cbz    x9, 0x189b6cd20           ; _objc_msgSend_uncached
   0x189b6ca0c <+108>: cmp    x12, x10
   0x189b6ca10 <+112>: b.eq   0x189b6ca1c               ; <+124>
   0x189b6ca14 <+116>: ldp    x17, x9, [x12, #-0x10]!
   0x189b6ca18 <+120>: b      0x189b6c9f4               ; <+84>
   0x189b6ca1c <+124>: b      0x189b6cd20               ; _objc_msgSend_uncached
   0x189b6ca20 <+128>: b.eq   0x189b6ca58               ; <+184>
   0x189b6ca24 <+132>: adrp   x10, 318626
   0x189b6ca28 <+136>: add    x10, x10, #0x600          ; =0x600 
   0x189b6ca2c <+140>: lsr    x11, x0, #60
   0x189b6ca30 <+144>: ldr    x16, [x10, x11, lsl #3]
   0x189b6ca34 <+148>: adrp   x10, 318626
   0x189b6ca38 <+152>: add    x10, x10, #0x568          ; =0x568 
   0x189b6ca3c <+156>: cmp    x10, x16
   0x189b6ca40 <+160>: b.ne   0x189b6c9b0               ; <+16>
   0x189b6ca44 <+164>: adrp   x10, 318626
   0x189b6ca48 <+168>: add    x10, x10, #0x680          ; =0x680 
   0x189b6ca4c <+172>: ubfx   x11, x0, #52, #8
   0x189b6ca50 <+176>: ldr    x16, [x10, x11, lsl #3]
   0x189b6ca54 <+180>: b      0x189b6c9b0               ; <+16>
   0x189b6ca58 <+184>: mov    x1, #0x0
   0x189b6ca5c <+188>: movi   d0, #0000000000000000
   0x189b6ca60 <+192>: movi   d1, #0000000000000000
   0x189b6ca64 <+196>: movi   d2, #0000000000000000
   0x189b6ca68 <+200>: movi   d3, #0000000000000000
   0x189b6ca6c <+204>: ret    
   0x189b6ca70 <+208>: nop    
   0x189b6ca74 <+212>: nop    
   0x189b6ca78 <+216>: nop    
   0x189b6ca7c <+220>: nop    

最笨的方法就是对每一个跟跳转地址有关的地方下断点,然后单步执行看最终去了哪里

小技巧是我发现这个objc_msgSend方法里面的跳转地址基本都是在函数内部跳转 ,从这个地址跳转到另一个地址, 所以我找了一个跳出当前方法的地址, 0x189b6cd20 ,我就对0x189b6cd20位置下了断点

image.png

image.png

命令行 按 C 跳转


libobjc.A.dylib`_objc_msgSend_uncached:
->  0x189b6cd20 <+0>:   pacibsp 
    0x189b6cd24 <+4>:   stp    x29, x30, [sp, #-0x10]!
    0x189b6cd28 <+8>:   mov    x29, sp
    0x189b6cd2c <+12>:  sub    sp, sp, #0xd0             ; =0xd0 
    0x189b6cd30 <+16>:  stp    q0, q1, [sp]
    0x189b6cd34 <+20>:  stp    q2, q3, [sp, #0x20]
    0x189b6cd38 <+24>:  stp    q4, q5, [sp, #0x40]
    0x189b6cd3c <+28>:  stp    q6, q7, [sp, #0x60]
    0x189b6cd40 <+32>:  stp    x0, x1, [sp, #0x80]
    0x189b6cd44 <+36>:  stp    x2, x3, [sp, #0x90]
    0x189b6cd48 <+40>:  stp    x4, x5, [sp, #0xa0]
    0x189b6cd4c <+44>:  stp    x6, x7, [sp, #0xb0]
    0x189b6cd50 <+48>:  str    x8, [sp, #0xc0]
    0x189b6cd54 <+52>:  mov    x2, x16
    0x189b6cd58 <+56>:  mov    x3, #0x3
    0x189b6cd5c <+60>:  bl     0x189b80368               ; lookUpImpOrForward
    0x189b6cd60 <+64>:  mov    x17, x0
    0x189b6cd64 <+68>:  ldp    q0, q1, [sp]
    0x189b6cd68 <+72>:  ldp    q2, q3, [sp, #0x20]
    0x189b6cd6c <+76>:  ldp    q4, q5, [sp, #0x40]
    0x189b6cd70 <+80>:  ldp    q6, q7, [sp, #0x60]
    0x189b6cd74 <+84>:  ldp    x0, x1, [sp, #0x80]
    0x189b6cd78 <+88>:  ldp    x2, x3, [sp, #0x90]
    0x189b6cd7c <+92>:  ldp    x4, x5, [sp, #0xa0]
    0x189b6cd80 <+96>:  ldp    x6, x7, [sp, #0xb0]
    0x189b6cd84 <+100>: ldr    x8, [sp, #0xc0]
    0x189b6cd88 <+104>: mov    sp, x29
    0x189b6cd8c <+108>: ldp    x29, x30, [sp], #0x10
    0x189b6cd90 <+112>: autibsp 
    0x189b6cd94 <+116>: braaz  x17
    0x189b6cd98 <+120>: nop    
    0x189b6cd9c <+124>: nop    

这个_objc_msgSend_uncached 感觉内部非常清晰, 里面只有一个 bl 语句, lookUpImpOrForward

控制台 b lookUpImpOrForward 或者直接看runtime源码

image.png

image.png

大致看了下汇编, 发现了和源码中有同样的方法名,所以接下来同学是喜欢看源码, 还是继续看汇编都可以

和initialize相关的我们找下

// Check for +initialize 检查是否initialize
// behavior应该是是否要初始化,我觉得默认肯定是大于0的 , 是不是一会儿汇编调试下 
// LOOKUP_INITIALIZE 全局搜索下 ,也是默认 = 1的
/*enum {
    LOOKUP_INITIALIZE = 1,
    LOOKUP_RESOLVER = 2,
    LOOKUP_CACHE = 4,
    LOOKUP_NIL = 8,
};*/

//!cls->isInitialized() :是否初始化 取反 , 也就是说, 没初始化的时候调用下面的函数
    if ((behavior & LOOKUP_INITIALIZE)  &&  !cls->isInitialized()) {
        initializeNonMetaClass (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, initializeNonMetaClass 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
    }

initializeNonMetaClass 函数:

image.png

把一个类传进来 , 如果有父类 并且 父类的initialized没有初始化, 就再次调用initializeNonMetaClass 并把父类传进去

具体如何初始化的initialize, 接着往下看, 如果不清楚是哪个方法, 就都点进去看看, 看看哪个像, 通过实验以及资料

image.png
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

让类对象去调用initialize方法 ,类对象通过isa指针 , 找到元类对象, 去元类对象的MethodList中去寻找initialize 

六.验证一些东西

1.behavior 是否大于0

OC函数:

x1, 是调用者 , 
x2,是方法名
x3 ~x几(我忘记了)的是参数

C函数:

x1开始就是参数了,所以我打印了x1, 到x3, 因为lookUpImpOrForward 就3个参数
image.png

所以behavior是等于3


完整的调用顺序 , 也可以不看这个, 直接看最后的结论

1.当给类发消息的时候 ,objc_msgSend会判断是否初始化过initialized,如果没有,就去调用initializeNonMetaClass初始化initialized
2.当初始化initialized的时候, 会判断类的父类是否初始化过initialized , 如果没有, 则优先初始化父类的initialized
3.如果有Category的话,也会遵循, 先去Category的方法里寻找initialized(这个涉及到Category方法和类里面的方法执行顺序的问题)
3.父类的initialize只初始化一次 ,但是会被多次调用
4.子类的 isa去元类对象methodlist找不到,就会通过superClass去父类找, 就会找到父类的initialize调用
5.如果去父类找initialize, 就会去看父类的methodlist方法, 但是methodlist 是包含了父类的category方法的, 根据源码, methodlist数组的顺序是category的方法放前面, 父类本身的方法放后面, methodlist数组中category的编译顺序根据源码的话是 build phases ->compile sources中的编译顺序决定的, 后编译的放数组前面 , 所以如果父类有category, 并且category中也实现了initialize就回去找父类category的initialize,


总结:

1.确实initialize的调用是在给类第一次发消息的时候调用的

2.当给类发消息的时候 ,objc_msgSend会判断是否初始化过initialized,如果没有,就去调用initializeNonMetaClass初始化initialized,在初始化之前, 会判断父类是否也初始化过initialize, 如果没有, 会优先初始化父类的initialize

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