OC对象的本质
我们平时编写的OC代码,最终转换为底层实现基本上绝大部分都是基于C\C++来实现的
下面展示OC代码最终编译转换的大致流程
OC -> C\C++ -> 汇编代码 -> 机器代码
也可以理解为OC的面向对象语法基本上都是基于C\C++的数据结构来实现的
那么OC中的类和对象,最终究竟是基于C\C++的哪种数据结构来实现的尼?
我们创建一个新工程,然后在main
函数中创建一个obj
对象,然后我们将这个main.m
文件的代码转换为cpp文件,测试代码如下:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSObject *obj = [[NSObject alloc] init];
}
return 0;
}
转换为cpp文件的命令如下:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
这句命令的格式如下:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx1 -o xxx2
其中的xxx1
代表的是OC源文件名
,也就是上面的main.m
其中的xxx2
代表的是输出的CPP文件名
,也就是上面的main.cpp
执行完上面的命令后,会生成一个main.cpp
的新文件,我们将此新文件拖拽到工程中,然后取消Add to Tagerts
的选中,也就是让工程不编译main.cpp
文件
我们在main.cpp
文件中搜索NSObject_IMPL
,找到如下结构体代码:
struct NSObject_IMPL {
Class isa;
};
从上面转换的cpp文件中的代码可以看到,NSObject
对象的底层数据结构就是一个结构体对象
我们看下NSObject
的OC定义如下:
OC中NSObject
最终转换为底层代码如下:
我们发现不管是OC中NSObject
定义,还是转换为底层的代码,在NSObject
的内部都包含了一个Class isa
指针
我们进入Class
内部定义查看,Class
定义如下:
typedef struct objc_class *Class;
我们发现Class
是一个指向objc_class
结构体的指针。既然Class
是一个指针,也就类似于void *
,NSObject
底层代码也就类似于下面代码:
struct NSObject_IMPL {
// 这里isa就是'void *'类型的指针
void * isa;
};
在C语言中,
void *
:表示不确定类型指针,void *
可以用来申明指针,例如:void * a
,这里a
就是void *
类型的指针
最终main
函数中创建的obj
对象,结构表示如下
NSObject *obj = [[NSObject alloc] init];
如图:
image
接下来我们再来探究下创建一个NSObject
对象系统会分配多少内存?
NSLog(@"---%zd",class_getInstanceSize([NSObject class])); // 8
NSLog(@"---%zd", malloc_size((__bridge const void *)obj)); // 16
class_getInstanceSize():表示NSObject的实例对象的成员变量所占用的内存大小
malloc_size():表示obj指针所指向的内存大小,也就是创建一个obj实例系统所分配的内存大小
需要注意:
虽然创建一个obj实例对象系统分配了16个字节的内存,但是obj对象内部真正用到的大小就只有前面8个字节,也就是用来存放isa指针的那8个字节大小,还剩余8个字节大小没有被使用,也就是说
NSObject_IMPL
结构体也就占用8个字节
对于class_getInstanceSize()
函数,我们可以通过查看objc底层源码如下:
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
通过alignedInstanceSize()
函数注释:
Class's ivar size rounded up to a pointer-size boundary.
可知class_getInstanceSize()
函数返回的确实就是实例对象成员变量所占用的内存大小
我们也可以查看objc底层源码来确认创建一个obj实例对象分配内存的情况
底层源码查看路径如下:
objc4源码 -> 全局搜索allocWithZone()-> NSObject.mm -> _objc_rootAllocWithZone() -> class_createInstance() -> _class_createInstanceFromZone() -> calloc() -> instanceSize()
在instanceSize()
函数中,我们可以看到如下定义:
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
从instanceSize
函数的注释可以看出,在OC中创建一个实例对象,系统最少分配16个字节内存
// CF requires all objects be at least 16 bytes.
我们再来查看下继承自NSObject
类的对象,系统分配多少内存?
我们来看如下代码:
// GQPerson的本质就是`struct GQPerson_IMPL`
@interface GQPerson : NSObject {
@public
int _no;
int _age;
}
@end
@implementation GQPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
GQPerson *person = [[GQPerson alloc] init];
person -> _no = 10;
person -> _age = 20;
// 可以将person对象转为`struct GQPerson_IMPL`类型对象,因为`GQPerson`类型的本质就是`struct GQPerson_IMPL`类型
struct GQPerson_IMPL *personImpl = (__bridge struct GQPerson_IMPL *)(person);
NSLog(@"%d -- %d", personImpl ->_no, personImpl ->_age); // 10 20
}
return 0;
}
将上面代码转换为objc底层代码如下:
struct NSObject_IMPL {
Class isa; // 8个字节
};
struct GQPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 这句等价于`Class isa`
int _no; // 4个字节
int _age; // 4个字节
};
通过转换后的底层代码GQPerson_IMPL
,我们知道创建一个GQPerson_IMPL
对象,系统分配了16个字节
我们通过下面的这两个函数打印也可以看出来,系统分配了16个字节内存
NSLog(@"---%zd",class_getInstanceSize([GQPerson class])); // 16
NSLog(@"---%zd", malloc_size((__bridge const void *)person)); // 16
示例图如下:
image下面我们再来看一个自定义类继承自另一个自定义类的例子,示例代码如下:
@interface Person : NSObject
{
int _age;
}
@end
@implementation Student
@end
@interface Student : Person
{
int _no;
}
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
}
return 0;
}
我们将示例代码转换为底层c++代码如下:
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
};
struct Student_IMPL {
struct Person_IMPL Person_IVARS;
int _no;
};
我们将上底层代码进行等价优化如下:
struct NSObject_IMPL {
Class isa;
};
// Person_IMPL结构体占用16个字节
// 结构体内存对齐:结构体占用内存大小,必须是其中最大成员占用内存的倍数
struct Person_IMPL {
Class isa; // 8
int _age; // 4
};
// Student_IMPL结构体占用16个字节,是因为_no的4个字节正好用在Person_IMPL结构体空余的4个字节上
struct Student_IMPL {
Class isa; 8
int _age; // 4
int _no; // 4
};
示例分析图如下:
image
通过分析优化的底层代码,我们可以知道person
和student
对象都占16个字节内存
下面我们再来看一个示例:
@interface Person : NSObject
{
int _age;
int _height;
int _no;
}
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [[Person alloc] init];
NSLog(@"%zd", sizeof(struct Person_IMPL)); // 24
NSLog(@"%zd", class_getInstanceSize([Person class])); // 24
NSLog(@"%zd", malloc_size((__bridge struct Person_IMPL *)person)); // 32
}
return 0;
}
我们将上面的代码转换为底层c++代码如下:
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8
int _age; // 4
int _height; // 4
int _no; // 4
};
我们通过打印可以看出,Person_IMPL
结构体所占用内存大小为24,这个正好符合结构体内存对齐原理,但是创建一个Person
实例对象为啥会分配32个字节尼,这个是苹果操作系统内存分配原则,具体参照底层代码:
底层源码查找路径如下:
libmalloc库 -> 搜索Buckets -> Buckets sized:
注释`
Buckets sized:
/* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */
我们从Buckets sized
的注释可以看出,苹果操作系统分配内存的原则:分配最小内存为16,最大为256,所有的分配的内存都是16的倍数。上面24个字节大小不是16的倍数,所以最终分配内存大小为32个字节
更多文章
- ReactNative开源项目OneM(1200+star):https://github.com/guangqiang-liu/OneM:欢迎小伙伴们 star
- 简书主页:包含多篇iOS和RN开发相关的技术文章http://www.jianshu.com/u/023338566ca5 欢迎小伙伴们:多多关注,点赞
- ReactNative QQ技术交流群(2000人):620792950 欢迎小伙伴进群交流学习
- iOS QQ技术交流群:678441305 欢迎小伙伴进群交流学习
网友评论