iOS基礎(七) - 聊一聊NSObject對象模型

前言

為什么要寫NSObject呢?嗯嗯,主要因為手賤。本來,近段時間公司比較閑,然后,鄙人想進階一下iOS開發,runtime之前看過一些簡書,blog等等,但是實際上用的比較少,也不是很清晰,所以就去看runtime的相關資料,然后發現很多不懂的知識,就不斷點連接,看看跳跳,然后就來到了NSObject對象模型,索性就先把它弄明白。推薦一篇深入淺出的文章《NSObject對象模型解析(上)》

1.先看一下結構

大家可以先看一下NSObject.h頭文件

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

- (Class)class;

上面可以看見,NSObject含有isa成員變量,是一個Class類型,繼續點進去,到objc.h。

typedef struct objc_class *Class;

Class是一個結構體指針,現在我們知道了isa是一個objc_class結構體指針。繼續點objc_class進去,到runtime.h。

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

激動人心的時刻,終于看到了這個經典的結構體objc_class。現在,我們來分析一下:

isa    //指向本類meta-class的結構體指針,meta-class存在的意義在于,調用類方法和調用實例方法是同一套消息傳遞機制。
super_class    //看名字就知道指向本類的結構體指針
name    //類名
version    //類版本信息
info    //類相關的一些信息
instance_size    //類所有實例變量的大小
ivars    //類的成員變量列表
methodLists    //函數列表
cache    //緩存使用過的方法列表,提高訪問速度
protocols    //協議列表

類的結構大概就是這樣的東西了,來實際編譯一下看源代碼

新一個類:ClassA
ClassA.h
@protocol ClassAProtocol <NSObject>

- (void)protocolMethod;

@end

@interface ClassA : NSObject<ClassAProtocol>

@property (nonatomic, strong) NSString *publicStr1;
@property (nonatomic, copy) NSString *publicStr2;

+ (void)classMethod;
- (void)publicMethod;

@end

ClassA.m
@interface ClassA ()

@property (nonatomic, strong) NSString *privateStr1;
@property (nonatomic, copy) NSString *privateStr2;

@end

@implementation ClassA {
    NSString *privateStr3;
}

#pragma mark - Class method
+ (void)classMethod {}

#pragma mark - Public
- (void)publicMethod {}

#pragma mark - Private
- (void)privateMethod {}

#pragma mark - ClassAProtocol
- (void)protocolMethod {}

@end

打開終端,進入ClassA.m文件所在目錄,執行下面命令

clang -rewrite-objc ClassA.m

打開生成的ClassA.cpp文件,定位到最底端,我們來一一分析。

找到ClassA.h聲明頭文件
#ifndef _REWRITER_typedef_ClassA
#define _REWRITER_typedef_ClassA
typedef struct objc_object ClassA;
typedef struct {} _objc_exc_ClassA;
#endif

extern "C" unsigned long OBJC_IVAR_$_ClassA$_publicStr1;
extern "C" unsigned long OBJC_IVAR_$_ClassA$_publicStr2;
extern "C" unsigned long OBJC_IVAR_$_ClassA$_privateStr1;
extern "C" unsigned long OBJC_IVAR_$_ClassA$_privateStr2;
struct ClassA_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *privateStr3;
    NSString *_publicStr1;
    NSString *_publicStr2;
    NSString *_privateStr1;
    NSString *_privateStr2;
};

上面可以看到我們定義的屬性和成員變量,我們再往下看:

static void _C_ClassA_classMethod(Class self, SEL _cmd) {}



static void _I_ClassA_publicMethod(ClassA * self, SEL _cmd) {}



static void _I_ClassA_privateMethod(ClassA * self, SEL _cmd) {}



static void _I_ClassA_protocolMethod(ClassA * self, SEL _cmd) {}

找到類方法,公有實例方法和私有實例方法,繼續往下看。

struct _objc_method {
    struct objc_selector * _cmd;
    const char *method_type;
    void  *_imp;
};

struct _protocol_t {
    void * isa;  // NULL
    const char *protocol_name;
    const struct _protocol_list_t * protocol_list; // super protocols
    const struct method_list_t *instance_methods;
    const struct method_list_t *class_methods;
    const struct method_list_t *optionalInstanceMethods;
    const struct method_list_t *optionalClassMethods;
    const struct _prop_list_t * properties;
    const unsigned int size;  // sizeof(struct _protocol_t)
    const unsigned int flags;  // = 0
    const char ** extendedMethodTypes;
};

struct _ivar_t {
    unsigned long int *offset;  // pointer to ivar offset location
    const char *name;
    const char *type;
    unsigned int alignment;
    unsigned int  size;
};

struct _class_ro_t {
    unsigned int flags;
    unsigned int instanceStart;
    unsigned int instanceSize;
    unsigned int reserved;
    const unsigned char *ivarLayout;
    const char *name;
    const struct _method_list_t *baseMethods;
    const struct _objc_protocol_list *baseProtocols;
    const struct _ivar_list_t *ivars;
    const unsigned char *weakIvarLayout;
    const struct _prop_list_t *properties;
};

struct _class_t {
    struct _class_t *isa;
    struct _class_t *superclass;
    void *cache;
    void *vtable;
    struct _class_ro_t *ro;
};

struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};

上面可以看到method,protocol,ivar以及class結構體定義,重點放在_class_ro_t這個結構體,是不是和runtime里面定義的objc_class結構體有點像。再看一下_class_t這個結構體,是不是除了包含了isa指針和superclass指針還包含_class_ro_t這個結構體,答案呼之欲出了,_class_t結構體就是objc_class編譯成c的結構。下面繼續看一下成員變量和屬性變成什么樣子。

static struct /*_ivar_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count;
    struct _ivar_t ivar_list[5];
} _OBJC_$_INSTANCE_VARIABLES_ClassA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_ivar_t),
    5,
    {{(unsigned long int *)&OBJC_IVAR_$_ClassA$privateStr3, "privateStr3", "@\"NSString\"", 3, 8},
     {(unsigned long int *)&OBJC_IVAR_$_ClassA$_publicStr1, "_publicStr1", "@\"NSString\"", 3, 8},
     {(unsigned long int *)&OBJC_IVAR_$_ClassA$_publicStr2, "_publicStr2", "@\"NSString\"", 3, 8},
     {(unsigned long int *)&OBJC_IVAR_$_ClassA$_privateStr1, "_privateStr1", "@\"NSString\"", 3, 8},
     {(unsigned long int *)&OBJC_IVAR_$_ClassA$_privateStr2, "_privateStr2", "@\"NSString\"", 3, 8}}
};

如上所示,成員變量都變成了類似OBJC_IVAR_$_ClassA$privateStr3這樣的結構,很容易就能看出來。看一下方法列表:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[11];
} _OBJC_$_INSTANCE_METHODS_ClassA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    11,
    {{(struct objc_selector *)"publicMethod", "v16@0:8", (void *)_I_ClassA_publicMethod},
    {(struct objc_selector *)"privateMethod", "v16@0:8", (void *)_I_ClassA_privateMethod},
    {(struct objc_selector *)"protocolMethod", "v16@0:8", (void *)_I_ClassA_protocolMethod},
    {(struct objc_selector *)"publicStr1", "@16@0:8", (void *)_I_ClassA_publicStr1},
    {(struct objc_selector *)"setPublicStr1:", "v24@0:8@16", (void *)_I_ClassA_setPublicStr1_},
    {(struct objc_selector *)"publicStr2", "@16@0:8", (void *)_I_ClassA_publicStr2},
    {(struct objc_selector *)"setPublicStr2:", "v24@0:8@16", (void *)_I_ClassA_setPublicStr2_},
    {(struct objc_selector *)"privateStr1", "@16@0:8", (void *)_I_ClassA_privateStr1},
    {(struct objc_selector *)"setPrivateStr1:", "v24@0:8@16", (void *)_I_ClassA_setPrivateStr1_},
    {(struct objc_selector *)"privateStr2", "@16@0:8", (void *)_I_ClassA_privateStr2},
    {(struct objc_selector *)"setPrivateStr2:", "v24@0:8@16", (void *)_I_ClassA_setPrivateStr2_}}
};

實例方法都變成了objc_selector的結構體指針,還包含系統默認自定義的get和set的方法,但是仔細一看,貌似方法列表少了類方法。為什么呢?大家看一下這個結構體的命名_OBJC$_INSTANCE_METHODS_ClassA,是不是想到了什么?是的,這個只是實例方法,類方法繼續往下看:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_ClassA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"classMethod", "v16@0:8", (void *)_C_ClassA_classMethod}}
};

_OBJC$_CLASS_METHODS_ClassA,這個命名夠明顯了吧,類方法列表。等等,我們是不是忘記了什么?有人會說,協議呢?協議方法呢?別急,下面給你呈現出來。

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_PROTOCOL_INSTANCE_METHODS_ClassAProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"protocolMethod", "v16@0:8", 0}}
};

struct _protocol_t _OBJC_PROTOCOL_ClassAProtocol __attribute__ ((used)) = {
    0,
    "ClassAProtocol",
    (const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_ClassAProtocol,
    (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_ClassAProtocol,
    0,
    0,
    0,
    0,
    sizeof(_protocol_t),
    0,
    (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_ClassAProtocol
};

是不是看見自己定義的協議和協議方法了。嘿嘿嘿,客官可真是慧眼呀??。再往下看。

static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_ClassA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"publicStr1","T@\"NSString\",&,N,V_publicStr1"},
    {"publicStr2","T@\"NSString\",C,N,V_publicStr2"}}
};

顯而易見,上面就是屬性列表。我們之前ClassA定義的屬性,成員變量,方法,協議都能找到相應的實現。

2.解析一下類(Class)與元類(meta-class)的關系

先上一張大家很熟悉的圖:


NSObject.png

先說一下,一個類的isa指針,meta-class以及superclass怎么獲取,代碼呈上:

Class object_getClass(id obj)    //獲取isa指針
objc_getMetaClass(const char *name)    //獲取meta-class
objc_getClass(const char *name)    //獲取本類
class_getSuperclass(Class cls)    //獲取父類

下面解析一下這張圖怎么得到的:

新建兩個類:ClassA繼承NSObject,ClassB繼承ClassA
@interface ClassA : NSObject

@end

@interface ClassB : ClassA

@end

同樣,clang一下,編譯成.cpp文件,我們再來逐步分析。
先看ClassA:

static void OBJC_CLASS_SETUP_$_ClassA(void ) {
    OBJC_METACLASS_$_ClassA.isa = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_ClassA.superclass = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_ClassA.cache = &_objc_empty_cache;
    OBJC_CLASS_$_ClassA.isa = &OBJC_METACLASS_$_ClassA;
    OBJC_CLASS_$_ClassA.superclass = &OBJC_CLASS_$_NSObject;
    OBJC_CLASS_$_ClassA.cache = &_objc_empty_cache;
}

從上面的代碼我們可以看出來:

ClassA.isa->ClassA_Meta_Class
ClassA.superclass->NSObject_Class
ClassA_Meta_Class.isa->NSObject_Meta_Class
ClassA_Meta_Class.superclass->NSObject_Meta_Class

我們再看ClassB:

static void OBJC_CLASS_SETUP_$_ClassB(void ) {
    OBJC_METACLASS_$_ClassB.isa = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_ClassB.superclass = &OBJC_METACLASS_$_ClassA;
    OBJC_METACLASS_$_ClassB.cache = &_objc_empty_cache;
    OBJC_CLASS_$_ClassB.isa = &OBJC_METACLASS_$_ClassB;
    OBJC_CLASS_$_ClassB.superclass = &OBJC_CLASS_$_ClassA;
    OBJC_CLASS_$_ClassB.cache = &_objc_empty_cache;
}

同樣,我們可以得到:

ClassB.isa->ClassB_Meta_Class
ClassB.superclass->ClassA
ClassB_Meta_Class.isa->NSObject_Meta_Class
ClassB_Meta_Class.superclass->ClassA_Meta_Class

結合ClassA和ClassB我們可以得出下圖:


NSObject1.png

是不是覺得和上面的圖還是有點不一樣,不著急,現在我們通過代碼來補全這張圖,順便驗證一下剛才畫的邏輯圖的正確性。GoGoGo!

ClassB *classB = [ClassB new];
Class myClass = [classB class];
while (myClass != nil) {
    Class superClass = class_getSuperclass(myClass);
    Class metaClass = object_getClass(myClass);
    Class superMetaClass = class_getSuperclass(metaClass);
    Class isaMetaClass = object_getClass(metaClass);
    const char *myClassName = class_getName(myClass);
    NSString *className = [NSString stringWithUTF8String: myClassName];
    NSLog(@"%@: %p\n%@_superClass: %p\n%@_meta_class: %p\n%@_meta_superclass: %p\n%@_isa_meta_class: %p\n", className, myClass, className, superClass, className, metaClass, className, superMetaClass, className, isaMetaClass);
    myClass = superClass;
}

//輸出
2017-03-02 22:05:49.520227 ObjCRuntimeDemo[9490:695334] ClassB: 0x100003b38
ClassB_superClass: 0x100003bd8
ClassB_meta_class: 0x100003b10
ClassB_meta_superclass: 0x100003bb0
ClassB_isa_meta_class: 0x7fffaec320f0
2017-03-02 22:05:49.520509 ObjCRuntimeDemo[9490:695334] ClassA: 0x100003bd8
ClassA_superClass: 0x7fffaec32140
ClassA_meta_class: 0x100003bb0
ClassA_meta_superclass: 0x7fffaec320f0
ClassA_isa_meta_class: 0x7fffaec320f0
2017-03-02 22:05:49.520570 ObjCRuntimeDemo[9490:695334] NSObject: 0x7fffaec32140
NSObject_superClass: 0x0
NSObject_meta_class: 0x7fffaec320f0
NSObject_meta_superclass: 0x7fffaec32140
NSObject_isa_meta_class: 0x7fffaec320f0
Program ended with exit code: 0

//每個類需要打印5個地址,分別是本類的地址,本類繼承的父類地址,本類元類地址,本類元類父類地址,本類元類isa指針指向的地址

由上述代碼以及輸出,我們驗證了第一張圖的正確性,并且進一步完善了第一張圖,如下:


NSObject2.png

看上去是不是和最開始的邏輯圖很像,再加上實例對象的isa指針的指向,替換一下子類,父類和根類的概念,完全就一樣了。所以說,類和元類之前的關系,就如上圖,每個類都有特定的元類,類的isa指針指向元類,元類擁有著類一樣的結構,并且每個元類的isa指針都是指向根元類,而元類的出現就是為了讓類方法的調用和實例方法保持一致。下面看類和元類編譯的c代碼:

extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject;

extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_ClassA __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_NSObject,
    0, // &OBJC_METACLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_METACLASS_RO_$_ClassA,
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;

extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_ClassA __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_ClassA,
    0, // &OBJC_CLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_CLASS_RO_$_ClassA,
};
//meta_class和class類型都是_class_t結構體

3.總結

上面說了一大堆,廢話也不少,第一次這么賣力寫文章,說起來都有點感動??。其實,主要就是NSObject對象模型的解析,以及類和元類的關系,大家有興趣的自己去編譯一下,肯定會有收獲的。

參考:

NSObject對象模型解析(上)
iOS:運行時消息傳遞
Objective-C Runtime

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容