細說OC中的load和initialize方法

發表於2016-03-02

OC中有兩個特殊的類方法,分別是loadinitialize。本文總結一下這兩個方法的區別於聯絡、使用場景和注意事項。Demo可以在我的Github上找到——load和initialize,如果覺得有幫助還望點個star以示支援,總結在文章末尾。

load

顧名思義,load方法在這個檔案被程式裝載時呼叫。只要是在Compile Sources中出現的檔案總是會被裝載,這與這個類是否被用到無關,因此load方法總是在main函式之前呼叫。

呼叫規則

如果一個類實現了load方法,在呼叫這個方法前會首先呼叫父類的load方法。而且這個過程是自動完成的,並不需要我們手動實現:

如果一個類沒有實現load方法,那麼就不會呼叫它父類的load方法,這一點與正常的類繼承和方法呼叫不一樣,需要額外注意一下。

執行順序

load方法呼叫時,系統處於脆弱狀態,如果呼叫別的類的方法,且該方法依賴於那個類的load方法進行初始化設定,那麼必須確保那個類的load方法已經呼叫了,比如demo中的這段程式碼,列印出的字串就為null

load方法的呼叫順序其實有跡可循,我們看到demo的專案設定如下:

執行順序

在Compile Sources中,檔案的排放順序就是其轉載順序,自然也就是load方法呼叫的順序。這一點也證明了load方法中會自動呼叫父類的方法,因為在demo的輸出結果中,Parentload方法先於Child呼叫,而它的裝載順序其實在Child之後。

雖然在這種簡單情況下我們可以辨別出各個類的load方法呼叫的順序,但永遠不要依賴這個順序完成你的程式碼邏輯。一方面,這在後期的開發中極容易導致錯誤,另一方面,你實際上並不需要這麼做。

使用場景

由於呼叫load方法時的環境很不安全,我們應該儘量減少load方法的邏輯。另一個原因是load方法是執行緒安全的,它內部使用了鎖,所以我們應該避免執行緒阻塞在load方法中。

一個常見的使用場景是在load方法中實現Method Swizzle:

Child類的load方法中,由於還沒呼叫Otherload方法,所以輸出結果是”Original Output”,而在main函式中,輸出結果自然就變成了”Swizzled Output”。

一般來說,除了Method Swizzle,別的邏輯都不應該放在load方法中實現。

initialize

這個方法在第一次給某個類傳送訊息時呼叫(比如例項化一個物件),並且只會呼叫一次。initialize方法實際上是一種惰性呼叫,也就是說如果一個類一直沒被用到,那它的initialize方法也不會被呼叫,這一點有利用節約資源。

呼叫規則

load方法類似的是,在initialize方法內部也會呼叫父類的方法,而且不需要我們顯示的寫出來。與load方法不同之處在於,即使子類沒有實現initialize方法,也會呼叫父類的方法,這會導致一個很嚴重的問題:

執行後發現父類的initialize方法竟然呼叫了兩次:

這是因為在建立子類物件時,首先要建立父類物件,所以會呼叫一次父類的initialize方法,然後建立子類時,儘管自己沒有實現initialize方法,但還是會呼叫到父類的方法。

雖然initialize方法對一個類而言只會呼叫一次,但這裡由於出現了兩個類,所以呼叫兩次符合規則,但不符合我們的需求。正確使用initialize方法的姿勢如下:

加上判斷後,就不會因為子類而呼叫到自己的initialize方法了。

使用場景

initialize方法主要用來對一些不方便在編譯期初始化的物件進行賦值。比如NSMutableArray這種型別的例項化依賴於runtime的訊息傳送,所以顯然無法在編譯器初始化:

總結

  1. loadinitialize方法都會在例項化物件之前呼叫,以main函式為分水嶺,前者在main函式之前呼叫,後者在之後呼叫。這兩個方法會被自動呼叫,不能手動呼叫它們。
  2. loadinitialize方法都不用顯示的呼叫父類的方法而是自動呼叫,即使子類沒有initialize方法也會呼叫父類的方法,而load方法則不會呼叫父類。
  3. load方法通常用來進行Method Swizzle,initialize方法一般用於初始化全域性變數或靜態變數。
  4. loadinitialize方法內部使用了鎖,因此它們是執行緒安全的。實現時要儘可能保持簡單,避免阻塞執行緒,不要再使用鎖。

相關文章