OC原始碼剖析物件的本質

一眼萬年的星空發表於2021-09-25

1. 類的底層實現

先寫一個 Person 類:

@interface Person : NSObject
@property (nonatomic, copy) NSString *p_name;
@property (nonatomic, assign) int p_age;

- (void)p_instanceMethod1;
@end

@implementation Person
- (void)p_instanceMethod1{
    NSLog(@"%s",__func__);
}
@end

 

使用 clang 編譯器, clang -rewrite-objc Person.m -o Person.cpp  將 Person.m  編譯成 Person.cpp 檔案,部分程式碼如下:

/// 1: Person 型別的底層結構
struct NSObject_IMPL {
  Class isa;
};

struct Person_IMPL {
  struct NSObject_IMPL NSObject_IVARS;
  int _p_age;
  NSString * _Nonnull _p_name;
};

/// 2: p_name 屬性的底層結構
// get
static NSString * _Nonnull _I_Person_p_name(Person * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_p_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
// set
static void _I_Person_setP_name_(Person * self, SEL _cmd, NSString * _Nonnull p_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _p_name), (id)p_name, 0, 1); }

/// 3: p_age 型別的底層結構 
// get
static int _I_Person_p_age(Person * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Person$_p_age)); }
// set
static void _I_Person_setP_age_(Person * self, SEL _cmd, int p_age) { (*(int *)((char *)self + OBJC_IVAR_$_Person$_p_age)) = p_age; }

/// 4: p_instanceMethod1 方法的底層結構
static void _I_Person_p_instanceMethod1(Person * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_0y_j3bzj65z6vw6hy1chw_4m0000gp_T_Person_f010c0_mi_0,__func__);
}

 

  • NSObject 類被編譯成了 NSObject_IMPL 的結構體。
  • Person 類被編譯成了 Person_IMPL 的結構體。
  • Person 類的內部還增加了一個 NSObject_IMPL 的結構體
    • 我們知道 Person 繼承於 NSObject, 所以它的底層實現中是第一個成員是父類的結構體,就是底層繼承的實現方式。用這樣的方式擁有父類所有的成員變數。
    • NSObject_IMPL 是 NSObject 類的編譯後的結構體,它的內部只有一個 Class 型別的 isa 成員變數。我們知道 isa 是 isa_t 型別的,那為什麼在這裡定義成 Class 型別呢?這是為了更加直觀的提現出它代表的是類的資訊,所以在獲取isa 的方法中,將它強制轉換成了Class 型別, 程式碼如下:
inline Class objc_object::ISA() {

    ...
    
    return (Class)(isa.bits & ISA_MASK)
}

 

總結:

1.類的底層實現是結構體。

2.繼承是通過把父類的結構體宣告為本類結構體的第一個成員變數實現的。

 

2. isa_t 的型別

聯合體: 所有成員可以是不同的型別,但是公用一塊記憶體區域,設定了一個成員變數就會覆蓋另一個成員變數的資料。優點是節省空間。

union isa_t { //聯合體
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    //提供了cls 和 bits ,兩者是互斥關係
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

 

isa 指標佔用8位元組,64位。64位中不同的位代表不同的含義:

 

物件與類的 isa 的指向關係

 

物件.isa -> 類.super -> 父類.super -> 根類.super -> nil

類.isa -> 元類.super -> 父元類.super -> 根元類.super -> 根類.super -> nil

元類.isa = 父元類.isa = 根元類.isa = 根元類

應用:判斷物件型別

下面的列印結果是什麼:

// [NSObject class] = NSObject
// object_getClass((id)[NSObject class]) = NSObject meta class

// 沿著 NSObject 的繼承者鏈去找根元類 -> 根類 == NSObject meta class 或者 NSObject meta class 的父類的例項
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[TestObject class] isKindOfClass:[TestObject class]];
BOOL res4 = [(id)[TestObject class] isMemberOfClass:[TestObject class]];

 

只有第一個是YES, 剩下的都是NO。 

isKindOfClass: 判斷自己的isa 指向的類是否等於傳入的類,不等於的話,找自己的繼承連中的父類看有沒有等於傳入的類,有則YES,沒有則NO

isMemberOfClass 判斷自己的isa 指向的類是否等於傳入的類,等於則YES,不等於則NO

 

原始碼:

// 類物件,是否是指定的元類的例項
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

// 例項物件,是否是指定的類的例項
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

// 類物件,是否是指定的元類cls的例項,或者是cls繼承者鏈中子類的例項
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->super_class) {
        if(tcls == cls) return YES;
    }
    return NO;
}
// 例項物件,是否是指定的類的例項,或者是cls繼承者鏈中子類的例項
-(BOOL)isKindOfClass:(Class)cls {
    for(Class tcls = [self class]; tcls; tcls = tcls->super_class) {
        if(tcls == cls) return YES;
    }
    return NO;
}

 青山不改,綠水長流,感謝每位佳人支援!

相關文章