iOS - initialize 方法探索

[toc]

參考

initialize

load

code

// NSObject 有實現(xiàn)該方法
+ (void)initialize;

objc4源碼解析

思路

既然 +initialize 方法會在類第一次接收到消息時被調(diào)用,

所以 objc_msgSend()方法內(nèi)部必然有調(diào)用 +initialize 榨呆。

但是 objc_msgSend() 的C源碼沒有開源, 只提供了匯編代碼水由。

我們只能通過其尋找方法的鏈路思考:

isa -> 類對象/元類對象, 查找方法, 調(diào)用; 若找不到, 查找父類 ↓

super_class -> 類對象/元類對象, 查找方法, 調(diào)用; 若找不到, 繼續(xù)查找父類 ↓

...

猜想, 在查找方法這一步, 有可能調(diào)用了 +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) {
    // 未被初始化, 就調(diào)用
    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;
    // 有父類, 且父類未初始化, 遞歸調(diào)用, 先初始化父類
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    callInitialize(cls);
    // ... 
}

// objc-initialize.mm
void callInitialize(Class cls) {
    // 調(diào)用了 initialize
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}
結(jié)論
調(diào)用方式

由系統(tǒng)自動調(diào)用, 不能手動調(diào)用

系統(tǒng)通過消息發(fā)送機制, objc_msgSend()

調(diào)用順序

先調(diào)用父類的 +initialize, 再調(diào)用子類的 +initialize

(如果子沒實現(xiàn)就調(diào)用父類的, 根類NSObject有實現(xiàn), 保證了不會報錯)

太亂不看
父類 / 分類

+initialize 是通過 objc_msgSend() 進(jìn)行調(diào)用的, 所以有以下特點:

  1. initialize 遵循繼承規(guī)則, 父類的 initialize 會比子類先執(zhí)行;

    如果被發(fā)消息的類及其分類 都沒有實現(xiàn) initialize, 則會查找并調(diào)用其父類的 initialize, 這種情況父類的initialize會被調(diào)用多次歌逢。

  2. 分類覆蓋主類, 如果被發(fā)消息的類的分類實現(xiàn)了+initialize 方法, 就會覆蓋這個類中的實現(xiàn)洗搂。

調(diào)用時機

在main()函數(shù)之后, 在該類或其子類收到第一條消息之前;
當(dāng)向該類發(fā)送的第一個消息, 一般是類消息首先調(diào)用, 常見的是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.

調(diào)用次數(shù)

結(jié)論:

不一定只被調(diào)用一次;

若子類及其分類未實現(xiàn) initialize, 父類的 initialize 會被調(diào)用多次;

但父類的initialize被調(diào)用多次, 并不代表父類被初始化多次, 僅僅是子類調(diào)用了父類的方法而已。

分析:

initialize 的調(diào)用與普通方法一樣都是使用 runtime 的消息發(fā)送機制腺阳。

  1. 若第一次給某個子類發(fā)送消息, 初始化該子類前需先初始化其父類, 此時父類的initialize被調(diào)用1次; (若此前父類已被初始化, 則不會再調(diào)用, 但也已經(jīng)被調(diào)用一次)

    消息機制會通過子類的isa找到子類的元類, 查找該元類的方法列表, 如果子類沒有實現(xiàn)initialize, 會通過元類的super_class指針查找父元類, 然后查找并調(diào)用父元類的initialize, 導(dǎo)致父元類的initialize被調(diào)用兩次; (類方法存儲在元類的方法列表)

    而如果子類沒有實現(xiàn)initialize, 會查找并調(diào)用父類的initialize, 導(dǎo)致父類的initialize被調(diào)用兩次;

    所以通常要在方法內(nèi)加一個判斷, 確認(rèn)當(dāng)前要初始化的是不是本類, 防止因子類的調(diào)用而將原本只需執(zhí)行一次的代碼執(zhí)行兩次缘圈。

  1. 另外, 初始化子類前, 系統(tǒng)會自動初始化父類, 子類不要手動調(diào)用super, 否則父類的initialize會被調(diào)用多次。

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

調(diào)用必然性

不一定被調(diào)用;

initialize 是懶加載的, 如果程序一直沒有給某個類或它的子類發(fā)送消息, 那么它永遠(yuǎn)不會被調(diào)用, 這一點有利于節(jié)省系統(tǒng)資源, 避免浪費野崇。


應(yīng)用場景

蘋果提供 initialize方法, 就是給開發(fā)者使用的, 第一次使用這個類的時候做一些事情称开。

initialize方法一般用于初始化全局變量 或 靜態(tài)變量。

無法在編譯期設(shè)定的全局變量, 可以放在initialize方法中初始化乓梨。

安全性

線程安全 線程阻塞

運行期系統(tǒng)會確保initialize方法是在線程安全的環(huán)境中執(zhí)行, 即只有執(zhí)行initialize的那個線程可以操作類或類實例鳖轰。其他線程都要先阻塞, 等待initialize執(zhí)行完

《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 的區(qū)別?
+ (void)initialize;  // 類方法, 每個類第一次被初始化的時候調(diào)用一次; (alloc)
- (instancetype)init;  // 對象方法, 每個對象初始化的時候調(diào)用一次;

initialize 的調(diào)用在 init 之前。

和 load 的區(qū)別

見【load - 面試題】

什么情況下initialize會被調(diào)用多次?

【見本文 - 調(diào)用次數(shù)】

initialize 初始化類指的是創(chuàng)建類對象?

應(yīng)該是 [TBC]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扶镀,一起剝皮案震驚了整個濱河市蕴侣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臭觉,老刑警劉巖昆雀,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蝠筑,居然都是意外死亡狞膘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門什乙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挽封,“玉大人,你說我怎么就攤上這事臣镣「ㄔ福” “怎么了智亮?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長点待。 經(jīng)常有香客問我鸽素,道長,這世上最難降的妖魔是什么亦鳞? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮棒坏,結(jié)果婚禮上燕差,老公的妹妹穿的比我還像新娘。我一直安慰自己坝冕,他們只是感情好徒探,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著喂窟,像睡著了一般测暗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上磨澡,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天碗啄,我揣著相機與錄音,去河邊找鬼稳摄。 笑死稚字,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的厦酬。 我是一名探鬼主播胆描,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼仗阅!你這毒婦竟也來了昌讲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤减噪,失蹤者是張志新(化名)和其女友劉穎短绸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旋廷,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡鸠按,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了饶碘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片目尖。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扎运,靈堂內(nèi)的尸體忽然破棺而出瑟曲,到底是詐尸還是另有隱情饮戳,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布洞拨,位于F島的核電站扯罐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏烦衣。R本人自食惡果不足惜歹河,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望花吟。 院中可真熱鬧秸歧,春花似錦、人聲如沸衅澈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽今布。三九已至经备,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間部默,已是汗流浹背侵蒙。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留傅蹂,地道東北人蘑志。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像贬派,于是被迫代替她去往敵國和親急但。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容