
神图镇楼
,相信做过iOS开发的同学一定非常熟悉这张经典图,每次看这张图都有不一样的体会,今天我们就借这张图,引出我们对类
的探索,主要探索的是 类的结构
分析以及isa
的走向和 继承
.
准备工作
objc781源码
创建两个类
ZGPerson
继承NSObject,定义一些方法和属性
@interface ZGPerson : NSObject{
NSInteger age;
}
@property (nonatomic, copy) NSString *name;
- (void) say666;
+ (void) say888;
@end
- (void)say666
{}
+ (void)say888
{}
@end
ZGTeacher
继承ZGPerson
@interface ZGTeacher : ZGPerson
@end
输出两个对象
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
ZGPerson *person = [ZGPerson alloc];
ZGTeacher *teacher = [ZGTeacher alloc];
NSLog(@"person %@\n teacher %@",person,teacher);
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
LLDB调试
利用LLDB
我们打印内存,先介绍几个常用指令
x/4gx
: 以16
进制形式打印地址内容,读取4
个16
字节内容
p/x
: 打印变量的16
进制格式
po
: 打印变量的description
方法
调试结果如下图所示

在我们调试的过程中发现p/x 0x001d8001000012d5 & 0x00007ffffffffff8ULL
和p/x 0x00000001000012a8 & 0x00007ffffffffff8ULL
中打印的信息都是ZGPerson
,但是两者的含义却不相同。
0x001d8001000012d5
是person
对象isa
指针所存储的类
的信息0x00000001000012a8 是
isa中获取的类信息所指的类的
isa的指针地址,即
ZGPerson类的
类isa
指针地址,我们称ZGPerson这样的类的类为元类
元类
什么是元类
?,主要有以下几点说明:
- 对象的
isa
是指向类,类的其实也是一个对象,可以称为类对象,其isa
的位域指向苹果定义的元类
元类
是系统给的,其定义和创建都是由编译器完成,在这个过程中,类
的归属来自于元类
元类
是类对象 的类,每个类都有一个独一无二
的元类
用来存储 类方法的相关信息。元类
本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称
isa
指针指向

由上图中LLDB调试信息我们得出isa
的指向:
-
person
实例对象的isa
指向了ZGPerson
类 -
ZGPerson
类对象的isa
指向了ZGPerson
元类
-
ZGPerson
元类对象的 isa 指向了NSObject
类 -
NSObject
类对象的isa
指向了自己
类的继承关系如下图:

类
的继承关系链:ZGTeacher(子类) --> ZGPerson(父类) --> NSObject(根类)-->nil
元类
的继承关系链:ZGTeacher(子元类) --> ZGPerson(父元类) --> NSObject(根元类)-->NSObject(根类)--> nil
为什么对象都有isa?
理清了isa
走位和继承问题,又来了一个新的疑问:为什么 对象
和 类
都有isa
属性呢?这里就不得不提到两个结构体类型:objc_class
& objc_object
我们在终端使用 clang
命令:
clang -rewrite-objc main.m -o main.cpp
在生成的.cpp
文件中我们注意到NSObject
的底层编译是NSObject_IMPL
结构体,其中Class
是isa
指针的类型,是由objc_class定义
的类型,
而objc_class
是一个结构体。在iOS中,所有的Class都是以objc_class
为模板创建的
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
我们在objc781
的源码中搜索objc_class :
发现在文件objc-runtime-new.h
中

可以发现结构体objc_class
是继承objc_object
结构体的(在c++中结构体是可以继承的),而我们搜索objc_object {
发现位于 objc-privat.h
的源码发现了isa
的定义!由此就可以解释为什么OC
中所有的对象都有一个isa
的指针!

总结:
- 结构体类型
objc_class
继承自objc_object
类型,其中objc_object
也是一个结构体,且有一个isa
属性,所以objc_class
也拥有了isa
属性- mian.cpp底层编译文件中,
NSObjec
t中的isa
在底层是由Class
定义的,其中class
的底层编码来自objc_class
类型,所以NSObject
也拥有了isa
属性NSObject
是一个类,用它初始化一个实例对象objc
,objc
满足objc_object
的特性(即有isa
属性),主要是因为isa
是由NSObject
从objc_class
继承过来的,而objc_class
继承自objc_object
,objc_object
有isa
属性。所以对象都有一个isa
,isa
表示指向,来自于当前的objc_object
objc_object
(结构体) 是 当前的 根对象,所有的对象都有这样一个特性objc_object
,即拥有isa
属性
类的结构体分析
类的结构体分析我们主要分析类的结构体
里面到底存储了哪些信息?
因为我们在分析类的结构体的时候需要用到内存偏移的知识,所以我们先补充一下内存偏移
.
我们从普通指针
,对象指针
,数组指针
逐个分析内存的偏移.
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//普通指针
int a = 10; //变量
int b = 10;
NSLog(@"%d -- %p", a, &a);
NSLog(@"%d -- %p", b, &b);
//对象指针
ZGPerson *p1 = [ZGPerson alloc];
ZGPerson *p2 = [ZGPerson alloc];
NSLog(@"%@ -- %p", p1, &p1);
NSLog(@"%@ -- %p", p2, &p2);
//数组指针
int c[4] = {1, 2, 3, 4};
int *d = c;
NSLog(@"%p -- %p - %p", &c, &c[0], &c[1]);
NSLog(@"%p -- %p - %p", d, d+1, d+2);
// Setup code that might create autoreleased objects goes here.
}
return 0;
}
打印结果

普通指针

-
a、b
都指向10
,但是a、b
的地址不一样,这是一种拷贝,属于值拷贝,也称为浅拷贝 -
a,b
的地址之间相差4
个字节,这取决于a、b
的类型
普通指针内存情况如图:

对象指针

-
p1、p2
是指针,p1
是 指向[ZGPerson alloc]
创建的空间地址,即内存地址,p2
同理 -
&p1、&p2
是 指向p1、p2
对象指针的地址,这个指针
就是二级指针
对象指针内存情况如图:

数组指针

-
&c
和&c[0]
都是取 首地址,即数组名等于首地址 -
&c
与&c[1]
相差4个字节,地址之间相差的字节数,主要取决于存储的数据类型
- 可以通过
首地址+偏移量
取出数组中的其他元素,其中偏移量是数组的下标,内存中首地址实际移动的字节数 等于 偏移量 * 数据类型字节数
数组指针内存情况如图:

类结构分析
接下来我们对objc781
中objc-runtime-new.h
文件中objc_class
结构体的源码进行分析,
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
...省略部分代码
通过首地址+内存偏移
我们可以打印objc_class
结构体中存储的信息.
-
isa
属性:继承自objc_object
,占8
个字节 -
superclass
属性:Class
类型指针,指针
占8
个字节 -
cache
:cache_t
是结构体
类型,结构体的内存大小有结构体本身的属性
决定,详情可参考本人的另一篇文章结构体与内存对齐 -
bits
:我们得到上面3个属性
的大小之后,通过首地址偏移
就可以得到bits
的内容
现在不能确定大小的就是结构体cache
的内存大小,我们进入cache
的源码中看一下
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
...一些static属性
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
...一些static属性
由于结构体中static的属性
不占内存大小,所以我们只要关注上面的几个属性的内存大小
-
_flags
属性:uint16_t
类型,uint16_t
是unsigned short
的别称,占2
个字节 -
_occupied
属性:uint16_t
类型,uint16_t
是unsigned short
的别称,占2
个字节 - 下面我们看
if-else
条件中各属性的大小
①if
流程:
_buckets
属性:struct bucket_t *
类型的结构体指针
类型,占8
个字节
_mask
属性:mask_t
类型,mask_t
是unsigned int
的别名,占4
字节
②elif
流程:
_maskAndBuckets
属性:uintptr_t
类型,是一个指针,指针占8
个字节
_mask_unused
属性:mask_t
类型,mask_t
是unsigned int
的别名,占4
字节
总结:cache
属性的内存大小:8+4+2+2 = 16
字节,所以我们想获取bits
属性中的内容我们需要偏移8+8+16
字节。
bits属性内容
通过LLDB
调试bits
内容如下图所示

其中data()
获取数据是objc_class
提供的方法
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
class_rw_t *data() const {
return bits.data();
}
...省略部分代码
通过LLDB获取方法列表

-
class_rw_t
中的methods()
函数,可以获取到方法列表
.
*list
函数获取列表的首地址,打印地址的值后,用get
查看到类中的所有方法。通过下图能看到类中定义的say666
方法、属性name
的setter
和getter
方法,还有一个系统生成的.cxx_destruct
方法: -
类中还定义了一个类方法
+ (void)say888
,在这里是看不到的。因为类方法存在元类
中。
通过LLDB获取属性列表

-
class_rw_t
中的properties()
函数,可以获取到属性列表
.
*list
函数获取列表的首地址,打印地址的值后,用get
查看到类中的所有属性。不过却看不到成员属性
,调用p $6.get(2)
会报错数组越界
成员属性
那么对象的成员属性到底存放在哪里呢?经过探索我们终于发现~

- 原来
成员变量
存在ro
中,通过函数ro()
获取,最终我们在ro
的ivars
属性中发现了我们的成员变量age
至此,大功告成!
总结一下我们这篇文章的内容
isa指针
指向及继承
关系- 探索为什么对象都有
isa
?- 类的结构体分析
- 补充知识-
内存偏移
网友评论