本文是Objective-C系列的第8篇,主要讲述OC对象的底层结构,以及分类:实例对象、类对象、元类对象。git
在上一篇Objective-C(七)对象内存分析分析后,咱们得知了一个类在内存中的存储。github
可是,咱们只分析了类的成员变量和属性,咱们知道,一个OC对象,还包括方法、协议等极其重要的信息,那么它们在哪里,又是如何存储,如何用的呢?数组
本篇在此进一步分析Objective-C类体系的分类及其在内存中的完整分布。bash
为此,咱们先要进行一些准备工做。app
Objective-C中的对象,简称OC对象,主要能够分为3种:布局
instance对象(实例对象)post
class对象(类对象)测试
meta-class对象(元类对象)ui
咱们针对OC中对象分为三种进行了测试,测试结果以下:this
从上图咱们能够获得如下结论:
那么测试代码中的几个方法,下面也简要说明下,部分截取源码项目-BFOCClass分类-01。
在NSObject.mm
中:
+ (Class)class {
return self;
}
复制代码
返回类对象(元类对象)自己。
因此,[[[NSObject class] class] class]
不管调用多少次class,其返回都是NSObject class对象。
返回实例对象的类对象
- (Class)class {
return object_getClass(self);
}
复制代码
由(2)中可知,
该方法与object_getClass从方法名看极其类似。
Class objc_getClass(const char *aClassName)
{
if (!aClassName) return Nil;
// NO unconnected, YES class handler
return look_up_class(aClassName, NO, YES);
}
复制代码
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
复制代码
返回传入obj
中isa
所指向的对象,传入的obj多是instance
对象、class
对象、meta-class
对象
其返回值对应为:
a) 若是是instance
对象,返回class
对象
b) 若是是class
对象,返回meta-class
对象
c) 若是是meta-class
对象,返回NSObject(基类)的meta-class
对象
因此上面测试中:
object_getClass(person1):返回即class
对象;
object_getClass(personClass5):返回即meta-class
对象;
返回是否为meta-class对象。
咱们会从源码摘抄一部分结构体,用来进行后面的论证。
NSObject
NSObject从源码中获得,其第一个成员变量为isa
,该Class
结构体第一个成员变量名称也是isa
。
isa
里则包含了(元)类信息的存放地址。
class_rw_t
class_rw_t
、class_ro_t
均是存放类中信息的结构体,包括成员变量、属性列表、协议列表及方法列表,其具体以下:
目前,咱们class_ro_t
当前类在编译期就已经肯定的属性、方法以及遵循的协议。
class_rw_t
则是在运行时,runtime从新加载布局的当前类的属性、方法和协议等。
method_t
method_t
是 方法最底层的结构。method_list_t
method_t
class_ro_t
中的成员变量。method_array_t
method_t
class_rw_t
中的成员变量;三者的关系以下图:
与method相似,property、protocol的相似。
至于为何会有一维和二维数组,后续文章会详述该问题。
property_t
property_array_t
与property_list_t
的结构与上面的method中的数组相似,分别是二维、一维数组。
struct property_t {
const char *name; //属性名
const char *attributes; //字符串,代表了该属性的特性
};
复制代码
假如属性名为name,类型为NSString,那么对应的attributes:T@"NSString",C,N,V_name
protocol_t
protocol_array_t
、protocol_list_t
分别是存放protocol_t
的二维及一维数组。
相似的,咱们关注protocol_t
结构体:
struct protocol_t : objc_object {
const char *mangledName;
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
uint32_t size; // sizeof(protocol_t)
uint32_t flags;
// Fields below this point are not always present on disk.
const char **_extendedMethodTypes;
const char *_demangledName;
property_list_t *_classProperties;
};
复制代码
ivar_t
咱们发现,针对成员变量,只有ivar_list_t
,而没有ivar_array_t
这样的结构。
从这里咱们能够知道,就算是runtime,也没法在运行时往类中添加成员变量。
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
};
复制代码
咱们已经对NSObject实例对象的内存结构至关熟悉了。咱们再阐述如下观点:
下面,咱们针对编译期的isa
,结合源码,咱们获得以下:
运行期的isa:
上面,咱们作了一些准备工做,了解了NSObject如何存储各类各样信息的结构体及存储在什么地方。
下面咱们将会针对,三种类型作一个简单的描述。
其内存布局以下:
instance对象在内存中存储的信息有:
isa
指针class对象在内存中存储的信息主要包括
isa
指针superclass
指针meta-class对象和class对象的内存结构是同样的,可是用途不同,在内存中存储的信息主要包括
isa
指针superclass
指针isa
在平时开发中不多使用,可是相信不少iOS开发者并不陌生。
在前面了解了NSObject、各类结构体及对象三大分类以后,苹果为咱们提供了的Cocoa/Cocoa Touch中,类蔟体系的创建依赖两个指针:isa
和superclass
。那么又是如何串联的?
横向联系:isa
的做用就是,将某一个类的instance对象、class对象、meta-class对象创建了横向联系,为查找对象存储各种信息提供指引。
纵向联系:superclass
则是面向对象语言中最为重要的特性——继承的体现,它体现的是类与类之间的联系,即为类图中创建了纵向联系。
请注意区分:一个类各类对象之间的联系,以及不一样类之间的联系!
咱们为了验证该结论,在真机设备上,运行项目-BFOCClass分类-01,尝试获取了BFPerson对象,并打印类相关地址:
BFPerson instance - 0x12fd1f570 0x12fd366e0
BFPerson class - 0x100091e28 0x100091e28 0x100091e28 0x100091e28 0x100091e28 0
BFPerson meta class - 0x100091e00 1 0x100091e28 0
(lldb) x/2xw 0x12fd1f570
0x12fd1f570: 0x00091e2d 0x000001a1
复制代码
从上面打印咱们得知:
isa
,此处为:0x1a100091e2disa
) & 0x0000000ffffffff8(ISA_MASK
) = 0x100091e28据此,isa
和superclass
联手织起的下面这张类图:
isa
指向
isa
指向classisa
指向meta-classisa
指向基类的meta-classsuperclass指向
superclass
指向父类的class 若是没有父类,superclass
指针为nilsuperclass
指向父类的meta-class 基类的meta-class的superclass
指向基类的classisa
找到class,方法不存在,就经过superclass
找父类isa
找meta-class,方法不存在,就经过superclass
找父类代码参考BFOCClass分类-01
继承关系:BFStudent
->BFPerson
->NSObject
BFStudent *stu = [[BFStudent alloc] init];
[stu test];
复制代码
BFStudent实现-(void)test;
,调用BFStudent的实现方法;
BFStudent未实现-(void)test;
,若BFPerson实现,调用BFPerson实现;
BFStudent和BFPerson均未实现,NSObject若实现,调用NSObject实现;
以上都未实现,代码是不能直接调用[stu test];
,会编译报错,须要改调用方式:
((**void** (*)(**id**, **SEL**))objc_msgSend)(stu, **@selector**(test));
复制代码
+(void)test;
,崩溃;-(void)test;
,调用-(void)test;
;继承体现中的类,均未实现,崩溃—— -[BFStudent test]: unrecognized selector sent to instance
[BFStudent test];
复制代码
BFStudent实现+(void)test;
,调用BFStudent的实现;
BFStudent未实现+(void)test;
,若BFPerson实现,调用BFPerson实现;
BFStudent和BFPerson均未实现,调用NSObject实现;
没法[BFStudent test];
调用,调用方式需改成:
Class stuClass = [BFStudent class];
((void (*)(id, SEL))objc_msgSend)(stuClass, @selector(test))
复制代码
+(void)test;
,调用+(void)test;
;-(void)test;
,调用-(void)test;
;+(void)test;
和-(void)test;
,调用+(void)test;
;继承体现中的类,均未实现,崩溃—— +[BFStudent test]: unrecognized selector sent to instance
针对状况1和2,大部分咱们都能理解,但有一种状况。是有差异的。
在BFStudent和BFPerson中均未实现对应的对象方法(类方法)时,都会去NSObject中寻找。
到此,咱们针对对象已经了解的差很少了,可是咱们仍是没有完整的看到运行时,对象的内部结构。
有两种方式:
咱们采用方式二。
抽象出的结构体从这里下载。
导入后,将编译项目调整为命令行项目,并将main.m改成main.mm,以支持C++编译。
对应的项目源码。
如今,你知道OC对象的本质了吗?它们的分类呢?