通过常见的面试题问题,来分析OC的底层技术,本文主要是学习小码哥课程关于底层的总结,只有记录下来和理解了,才能转变为自己的。
一 、查看OC的底层实现
-
我们平时编写的Object-C代码,底层都是基于C、C++的数据结构实现的
OC代码转化的过程
先思考几个问题:
1、 Object-C的对象、类主要是基于C、C++的什么数据结构实现的呢?
2、NSObject对象在内存中占几个字节?
2、一个 Object-C对象,在内存中是如何布局的?
下面我们来创建一个Object-C对象,来转换为C++的文件,来分析OC的底层实现。
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
return 0;
}
cd到main.m的目录文件,然后使用clang命令行
//未指定终端架构,生成了main.cpp文件
clang -rewrite-objc main.m -o main.cpp
//指定arm64位架构,生成main-arm64.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
直接点NSObject,进去查看OC中的实现方式
// NSObject在底层的实现
@interface NSObject {
Class isa ;
}
我们打开arm64.cpp 文件,在里面找到
// NSObject在C++中的实现
struct NSObject_IMPL {
Class isa;
};
// Class的本质是一个指针
typedef struct objc_class *Class;
发现在C++中,OC是以一个结构体实现的,而结构体中只有一个成员变量isa,点击Class进去查看,发现原来isa是一个指针。
所以,可以得出结论:NSObject主要是基于C、C++的结构体实现的,结构体里面只有一个isa成员变量,而isa又是一个指针,一个指针在64位中占的是8个字节,所以NSObject对象在64位中内存占8个字节。
同时我们也可以用代码打印,查看一个对象在内存中占用的字节。
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
// 获得NSObject实例对象的成员变量所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
// 获得obj指针所指向内存的大小 >> 16
// 返回指针所指向对象字节数
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
}
return 0;
}

打印结果同时印证了上面的出的结论。
创建NSObject对象,在64位架构中分配8个字节的内存空间,用来存放一个成员isa指针。那么isa指针这个变量的地址就是结构体的地址,也就是NSObjcet对象的地址。
假设isa的地址为0x100400110,那么上述代码分配存储空间给NSObject对象,然后将存储空间的地址赋值给objc指针。objc存储的就是isa的地址。objc指向内存中NSObject对象地址,即指向内存中的结构体,也就是isa的位置。
那么NSObject中的其它属性和方法难道不占用内存吗?存在了那里?
当然占用内存,这个问题后面讲NSObject的对象分类再来解决。
二 、自定义类的内部实现
面试问题:自定义类的对象在内存中是如何分布的。
问题:自定义People类,声明_num和_age,则该People对象在内存中占几个字节?
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
/* Person类 */
@interface People : NSObject{
@public
int _num;
int _age;
}
@end
@implementation People
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *p = [[People alloc] init];
p ->_num = 4;
p ->_age = 5;
NSLog(@"%@",p);
}
return 0;
}
我们先自定义People类,通过转化为C++文件,可以查看People的底层实现:
People在底层的实现
struct People_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _num;
int _age;
};
我们假设 struct NSObject_IMPL NSObject_IVARS;相等于 Class isa;
那么People在底层的实现即为
struct People_IMPL{
Class isa;
int _num;
int _age;
};
结构体People_IMPL有3个成员变量,分别是isa,_num,_age,而我们知道isa是一个指针类型,在64位架构中占8个字节,_num和_age分别为int类型,在64位架构中分别占4个字节,那么People_IMPL结构体则占16个字节,因此People对戏那个占16个字节。
下面用代码打印,查看People对象在内存中占几个字节。
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *p = [[People alloc] init];
p ->_num = 4;
p ->_age = 5;
NSLog(@"======%@",p);
// 1.通过OC的方式,分析占用内存
NSLog(@"num= %d age= %d",p->_num,p->_age);
// 2.通过结构体,分析占用内存
// 把OC对象转为底层的结构体(相当于把指向OC对象的指针,转化为指向结构体的指针)
struct People_IMPL *pImpl = (__bridge struct People_IMPL *)p;
NSLog(@"num= %d age= %d",pImpl ->_num,pImpl ->_age);
// 3. 通过runtime机制,查看占用内存
NSLog(@"%zd %zd",
class_getInstanceSize([NSObject class]),
class_getInstanceSize([People class]));
}
return 0;
}

通过打印结果可知,num和age为int类型,分别占4个字节,而NSObject占8个字节,所以People对象一共占16个字节,而最后打印的结果也为16个字节,所以我们前面在C++中的分析成立。
三、窥探内存结构
方式一:先断点,选中断点对象p,右键点击view Memory of "*p"

通过打印结果,可以分析出
方式二:通过LLDB指令
lldb是xcode自带的调试器,简单介绍几个指令
print、po 打印
lldb读取内存
memory read/数量 格式 字节数 内存地址
简写 x/数量 格式 字节数 内存地址
格式 x是16进制,f是浮点,d是10进制
字节大小 b:byte 1字节,h:half word 2字节
w:word 4字节,g:giant word 8字节
根据地址修改内存中的值 : memory write 地址

四、OC对象的分类
OC对象主要分为3类,分别是instance对象(实例对象)、class(类对象)、meta-class(元类对象)。
1. instance对象
instance对象:就是通过类alloc初始化的对象,每次都会产生新的instance对象;
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
obj1、obj2即为NSObject的instance对象,它们为两个不同的对象,分别占据着两个不同的内存空间
创建instance对象
People * obj1 = [[People alloc] init];
obj1->_age = 3;//0x10044b190
People * obj2 = [[People alloc] init];
obj2->_age = 4;// 0x10044b1e0

通过上面的,我们可以看出,obj1、obj2分别占据着两个不同的内存
instance对象在内存中存储的信息包括
1、isa指针
2、其他成员变量
2. class对象
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
/* Person类 */
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
/* Person类 */
@interface People : NSObject{
@public
int _age;
}
@property (nonatomic,assign) int num;
- (void)getPeopleClassMethod;
+ (void)addClassMethod;
@end
@implementation People
- (void)getPeopleClassMethod{
}
+ (void)addClassMethod{
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *obj1 = [[People alloc] init];
obj1->_age = 3;
[obj1 getPeopleClassMethod];
[People addClassMethod];
}
return 0;
}
Class对象在内存中存储的信息主要包括
1、isa指针
2、superclass指针
3、类的属性信息
4、类的对象方法信息
5、类的协议信息
6、类的成员变量
NSObject *object1 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [NSObject class];
// runtime
Class objectClass3 = object_getClass(object1);
NSLog(@"%p %p %p", objectClass1, objectClass2, objectClass3);

说明它们是同一个对象,每个类在对象中有且只有一个Class对象
3、meta-class对象
Class objectClass = object_getClass([NSObject class]);
NSLog(@"%p", objectClass4);
objectClass是NSObject的meta-class对象(元对象)
1、isa指针
2、superclass指针
3、类的类方法信息
meta-class的对象和class对象的内存结构是一样的,但是它们的用途不一样
补充几点:
1、查看Class对象是否为meta-class对象
BOOL result1 = class_isMetaClass([NSObject class]);
Class objectClass4 = object_getClass([NSObject class]);
BOOL result2 = class_isMetaClass([objectClass4 class]);
NSLog(@"result===%d=======%d",result1,result2);
2018-08-07 15:52:19.165447+0800 OC对象的本质-自定义类02[3428:227786] result===0=======1
2、上面提过NSObject中的其它属性和方法难道不占用内存吗?存在了那里?
相信到这,我们已经知道了答案。
成员变量的具体值存放在instance对象。
对象方法,协议,属性,成员变量信息存放在class对象。
类方法信息存放在meta-class对象。
有些地方可能我还没有被理解透彻,望指点!
网友评论
大兄弟