Objc 中任何物件都可以稱之為 id
型別,那麼看下在 objc.h
對 id
型別的定義:
/// 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 的原始碼可以得出以下結構:
注:
class_ro_t
中存放的是編譯時可以確定的屬性、方法和協議等
- Objc 中的物件是一個
objc_object
結構體,結構體中第一個變數是isa_t
,存放著該物件所屬類的資訊; - 類是一個
objc_class
結構體,繼承自objc_object
,所以類也是一個物件,另外還有兩個變數進行方法快取和資料存放,比如變數、方法(例項方法,以-
開頭的方法)和所遵守的協議。 - 類的
isa
變數中存放的類是元類(meta-class),類是一個物件,物件的型別就是元類,元類存放著類的方法(類方法,以+
開頭的方法)。 - 元類也是類,所以元類也是物件。元類
isa
變數中存放的類是根元類(一般是NSObject
)。 - 根元類
isa
指向其自身 - 類和元類都是單例
做個試驗,新建一個 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_obj
的 bb_name
屬性賦值,卻列印出 name
值的原因,在 iOS 中,棧的地址是由高到低,堆的地址是由低到高,在這段程式碼中棧中依次壓入了 name
、bb_obj
,而 bb_obj
物件自身的屬性是根據自身首地址進行偏移去獲取,所以會取到 name
的值。
使用 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/