runtime(零) Objc 中類和物件的本質

wxiubin發表於2017-12-21

Objc 中任何物件都可以稱之為 id 型別,那麼看下在 objc.hid 型別的定義:

/// A pointer to an instance of a class.
typedef struct objc_object *id;
複製程式碼

註釋中的描述是 一個指向類的例項的指標,那麼是不是意味一個類的例項即物件就是一個 objc_object 結構體呢?再看原始碼:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

@interface Object { 
    Class isa; 
}

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
}
複製程式碼

通過閱讀 runtime 的原始碼可以得出以下結構:

runtime(零) Objc 中類和物件的本質

注:class_ro_t 中存放的是編譯時可以確定的屬性、方法和協議等

  1. Objc 中的物件是一個 objc_object 結構體,結構體中第一個變數是 isa_t ,存放著該物件所屬類的資訊;
  2. 類是一個objc_class 結構體,繼承自 objc_object ,所以類也是一個物件,另外還有兩個變數進行方法快取和資料存放,比如變數、方法(例項方法,以 - 開頭的方法)和所遵守的協議。
  3. 類的 isa 變數中存放的類是元類(meta-class),類是一個物件,物件的型別就是元類,元類存放著類的方法(類方法,以 + 開頭的方法)。
  4. 元類也是類,所以元類也是物件。元類 isa 變數中存放的類是根元類(一般是 NSObject )。
  5. 根元類 isa 指向其自身
  6. 類和元類都是單例

做個試驗,新建一個 BBObject 類,新增一個屬性 bb_name,標頭檔案如下:

@interface BBObject : NSObject

@property (nonatomic, strong) NSString *bb_name;

@end
複製程式碼

那麼以下程式碼會輸出什麼呢:

NSString *name = @"這是 bb_name";
void *cls = (__bridge void *)([BBObject class]);
void *bb_obj = &cls;
NSLog(@"%@",[(__bridge BBObject*)bb_obj bb_name]);
複製程式碼

輸出的是:2017-12-16 19:55:33.477716+0800 runtime[45876:7116185] 這是 bb_name

因為 Objc 中一個物件就是 首地址指向一個類的連續空間,為什麼是連續空間?那是因為物件還有自己屬性變數的值要儲存,這也是為什麼沒有給 bb_objbb_name 屬性賦值,卻列印出 name 值的原因,在 iOS 中,棧的地址是由高到低,堆的地址是由低到高,在這段程式碼中棧中依次壓入了 namebb_obj,而 bb_obj 物件自身的屬性是根據自身首地址進行偏移去獲取,所以會取到 name 的值。

runtime(零) Objc 中類和物件的本質

使用 clang -rewrite-objc BBObject.m 可以把得到重寫後的 C++ 檔案,在其中也可以看到其中獲取屬性就是自身地址加偏移量:

static NSString * _I_BBObject_bb_name(BBObject * self, SEL _cmd) { 
    return (*(NSString **)((char *)self + OBJC_IVAR_$_BBObject$_bb_name)); 
}
複製程式碼

我的部落格:iosgg.cn/

相關文章