作为一个iOS搬砖工,越来越感觉每天的重复搬砖工作没能给自己带来多大的提升,在这种不满足的情况下,是时候探索一下iOS底层原理实现,这篇文章将做作为开篇,记录这段时间学习底层原理的过程。
首先抛出三个面试题:
- 一个NSObject对象占用多少内存?
- 对象的isa指针指向哪里?
- OC的类信息存放在哪里?
一、一个NSObject对象占用多少内存?
大家都知道OC语言是C语言的超集,是对C语言上增加面向对象特性,大大简化开发人员编写代码量和不用怎么管内存管理。我们平时写的OC代码,其底层实现其实是C、C++代码,所以可以这么说:Objective-C的面向对象都是基于C、C++的数据结构实现的。再抛出一个问题,是基于C、C++什么数据结构呢?
论证:
1.新建一个命令行项目:在main.m编写以下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
}
return 0;
}
打开终端,定位到main.m文件所在位置,执行命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
得到main.cpp,从后缀名可以看出这是C++文件,代码很长,我们只需要关注最后的代码:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
return 0;
}
从转化后的C++代码可以看出NSObject调用alloc、init方法都是调用runtime的objc_msgSend方法,这里貌似看不太出什么东西,我们继续搜索NSObject,最后搜到一个这样的代码:
struct NSObject_IMPL {
Class isa;
};
从_IMPL(类似@implementation) 可以看出这里就是NSObject这个对象的具体实现,到这里已经明白,这就是一个C语言的结构体,里面包含一个Class类型的isa指针。
回到问题1,OC对象占用多少内存:
方法1:
我们可以通过runtime的class_getInstanceSize方法获取实例对象占用内存:
方法2:
通过malloc_size()方法
size2.png
可能有读者会有点奇怪,方法1和方法2获取的大小不一样。这是由于计算机原理的“内存对齐”原理,系统会给NSObject对象分配16个字节大小,实际占用是8个字节大小。
方法三:Debug -> Debug Workfllow -> View Memory (Shift + Command + M)[图片上传中...(WX20190510-143756@2x.png-92f2e4-1557470289122-0)]
image.png
WX20190510-143756@2x.png
方法四:通过LLDB命令,x + 对象或对象地址
WX20190510-144045@2x.png扩展:
对于一个自定义的类,占用多少内存?如一个Person类,包含四个成员属性:
NSString *name,
CGFloat weight,
CGFloat height,
NSInteger age,
问:Person类对象占用多少内存空间?
欢迎评论区讨论。
二、对象的isa指针指向哪里?
首先,OC对象分三种类型:
1-instance对象(实例对象)
2-class对象(类对象)
3-meta-class对象(元类对象)
1.instance对象(实例对象)
就是通过alloc出来的类对象如
NSObject *obj1 = [[NSObject alloc] init];
SObject *obj2 = [[NSObject alloc] init];
object1、object2是NSObject的instance对象(实例对象)
它们是不同的两个对象,分别占据着两块不同的内存
instance对象在内存中存储的信息包括:
isa指针
其他成员变量
新建一个Person类:
@interface Person : NSObject
{
int _age;
int _name;
int _no;
NSString *name;
}
同样转成C++代码,发现Person的实现代码如下:
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
int _no;
NSString *name;
};
第一个成员是struct NSObject_IMPL NSObject_IVARS的结构体,搜索NSObject_IMPL发现代码:
struct NSObject_IMPL {
Class isa;
};
发现就是一个Class类型的isa,因此Person类的实现可以转化成:
struct Person_IMPL {
Class isa;
int _age;
int _no;
NSString *name;
};
由此可以看出来,无论是NSObject最简单的类还是自定义的类,第一个结构体成员永远都是isa,所以isa的地址==对象地址,我们可以通过打印论证一下:
image.png
2-class对象(类对象)
获取类对象有2种方式:
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];
NSLog(@"实例对象%p %p",object1,object2);
NSLog(@"类对象%p %p %p %p %p",
objectClass1,
objectClass2,
objectClass3,
objectClass4,
objectClass5);
打印结果如下:
WX20190510-153059@2x.png
objectClass1 ~ objectClass5都是NSObject的class对象(类对象)
它们是同一个对象。每个类在内存中有且只有一个class对象
class对象在内存中存储的信息主要包括
1-.isa指针
2-.superclass指针
3-.类的属性信息(@property)
4-.类的对象方法信息(instance method)
5-.类的协议信息(protocol)
6-.类的成员变量信息(ivar)
WX20190510-163744@2x.png
3-meta-class对象(元类对象)
获取元类对象方法
Class meteltClass1 = object_getClass([NSObject class]);
objectMetaClass是NSObject的meta-class对象(元类对象)
每个类在内存中有且只有一个meta-class对象
meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括
1-.isa指针
2-.superclass指针
3-.类的类方法信息(class method)
......
回到问题本身,类的isa到底指向哪里?我们已经分析了一个类的对象分三种,而且这三种都有一个共同的成员isa,这个isa就是用来关联着三种类对象的,可以这么说:一个类的实例对象可以通过isa找到所在的类对象,类对象又可以通过isa找到它的元类对象,它们的关系如下:
image.png
instance的isa指向class
当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用
class的isa指向meta-class
当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用
为了验证我的猜想,只能从官网下载objc源码:
-
https://opensource.apple.com/tarballs/objc4/
搜索objc_class WX20190510-172112@2x.png
真正的结构长这样:
image.png
另外补充一点,前面说到实例对象的isa指向类对象地址,按道理说isa存储的值就是类对象的地址,我们打印一下:
image.png
很明显两个值并不相同,原因是,从64bit开始,isa值会跟ISA_MASK相与,得到的新值才是类对象的地址,通过搜索源码可知
arm64下ISA_MASK的值为0x0000000ffffffff8
x86下的ISA_MASK值为0x00007ffffffffff8,打印如下:
image.png
两个值都为0x00007fff96bb9140,验证了我们的结论。所以上面的三种类对象isa之间的关系实际为:
image.png
三、OC的类信息存放在哪里?
这个问题还要答吗?看上图。
总结:
一、一个NSObject对象占用多少内存?
1-.系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
2-.但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
二、对象的isa指针指向哪里?
1-. instance对象的isa指向class对象
2-.class对象的isa指向meta-class对象
3-.meta-class对象的isa指向基类的meta-class对象
三、OC的类信息存放在哪里?
1-.成员变量的具体值,存放在instance对象
2-.对象方法、属性、成员变量、协议信息,存放在class对象中
3-.类方法,存放在meta-class对象中
网友评论