分類不能自動建立 get set 方法

林欣達發表於2017-01-15

前言

前幾天有人問我一個問題:為什麼分類不能自動建立get set方法。老實說,筆者從來沒有去思考過這個問題。於是這次通過程式碼實踐跟runtime原始碼來探究這個問題。

準備工作

為了能減少輸出類資料的程式碼工作,筆者基於NSObject的分類封裝了一套程式碼

 11783864-c416370fe938ea40

其中輸出類例項變數的具體程式碼:

+(void)kRecordOBJ採用dispatch_once的方式將NSObject存在的資料儲存到三個陣列中,用來排除父類的資料輸出

類的屬性

  • 正常建立類

    執行結果:屬性nameage生成了對應的_propertyName的例項變數以及settergetter
     12783864-eae915b82abc97ca
  • 動態生成屬性age

    執行結果:缺少了_age變數以及對應的setAge:age方法
     13783864-63584be595c2c024
  • 手動實現setter/getter

    輸出結果:未生成_age例項變數
     14783864-b81880b32b2e6dba
  • 手動實現_pIdsetter/getter

    執行結果:KVC的訪問會觸發setter方法,_pId除了無法通過點語法訪問外,其他表現與@property無異
     15783864-4b6419ceb5605328

通過上面的幾段試驗,可以得出@property的公式:

 16783864-a859e0163a20a5b7

分類屬性

  • 分類中新增weighheight屬性

    執行結果:weighheight未生成例項變數以及對應的setter/getter,與@dynamic修飾的age表現一致
     17783864-7fabb41cc79e1d02
  • 使用@synthesize自動合成setter/getter方法時編譯報錯
     18783864-094fbbf37d5136e1
  • 手動實現setter/getter
    @implemetation Person (category)

    執行結果:與@dynamic age後重寫其setter/getter表現一致
  • 動態繫結屬性來實現setter/getter

    執行結果:動態繫結前後ivar沒有發生任何變化
     19783864-5ead3ca71e138237

通過程式碼實驗,可以得出下面兩個結論:

  • 分類屬性相當於@dynamic property
  • 缺少ivar的情況下無法使用@synthesize自動合成屬性

以及一個猜想:

  • 在類完成載入後無法繼續新增ivar

通過runtime動態建立類驗證猜想:

執行結果:在呼叫class_registerClassPair後,新增ivar失敗

 20783864-c313e5684273bfb6

從原始碼解析

objc_class的結構體定義如下:

ps: 在新版本中結構體內部已經發生了大改,但是內部的屬性大致上仍是這些

這裡面有個重要的屬性ivar_layout,顧名思義存放的是變數的位置屬性,與之對應的還有一個weakIvarLayout變數,不過在預設結構中沒有出現。這兩個屬性用來記錄ivar哪些是strong或者weak,而這個記錄操作在runtime階段已經被確定好。正由於如此,這極有可能是ivar無法在類被載入後繼續新增的原因之一。ivar_layout的更多瞭解可以參照Objective-C Class Ivar layout一文

import操作幫助編譯檢查和連結過程,但是在category的載入過程中,不會將擴充套件的內容新增到原始的類結構中。runtime對於category的載入過程可以簡單的分成下面幾步(摘自objc category的密碼):

  • objc runtime的載入入口是一個叫_objc_init的方法,在library載入前由libSystem dyld呼叫,進行初始化操作
  • 呼叫map_images方法將檔案中的image map到記憶體
  • 呼叫_read_images方法初始化map後的image,這裡面幹了很多的事情,像load所有的類、協議和category,著名的+ load方法就是這一步呼叫的
    -仔細看category的初始化,迴圈呼叫了_getObjc2CategoryList方法,這個方法拿出來看看:
  • .…

這一切的過程發生在_objc_init函式中,函式實現如下

 21783864-10118519d17db585

簡單來說在load_images函式中最終會走到下面的程式碼呼叫來載入所有的類以及類的分類

 22783864-a7b14481843bed47

根據上面的程式碼加上runtime的載入順序,可以繼續推出:

  • @dynamic實際上是將屬性的載入推遲到類載入完成後

另外,前面也說過在缺少ivar的情況下無法自動合成setter/getter,除了category本身是不被新增到類結構中的,所以無法使用類結構的ivar合成屬性外,還有分類自身結構的問題

可以看到分類結構本身是不存在ivar的容器的,因此缺少了自動合成屬性的條件。最後還有一個問題,我們在使用objc_associate系列函式繫結屬性的時候這些變數儲存在了哪裡?

 23783864-4cf8e35692b5e390

runtime底層,存在一個型別為AssociationHashMap的雜湊對映表儲存著物件動態新增的屬性,每個物件以自身地址為key維護著一個繫結屬性表,我們動態新增的屬性就都儲存在這個表裡。

上一篇:閒聊記憶體管理

相關文章