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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容