iOS底层原理--OC对象的本质
1、NSObject的本质是什么?
分析: OC代码的底层实现实质是C/C++代码,继而编译成汇编代码,最终变成机器语言。
打开终端,进入main.m所在的文件夹,通过clang rewirte-objc main.m -o main.cpp
或xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
代码,生成cpp文件。
在cpp文件中找到如下代码:
struct NSObject_IMPL {
Class isa;
}
NSObject的底层实现实质是一个结构体。而结构体中的成员isa
是Class类型,通过源码typedef struct objc_class *Class
可知它是一个指针。在64为环境下指针占8个字节,而在32位机下是占4个字节。因此该结构体占8个字节(因为该结构体只有一个成员)。
2、NSObject对象占多少内存?
分析: 通过打印
NSObject *obj = [[NSObject alloc]init];
NSLog(@"%zd",malloc_size((__bridge const void *)obj));
可知NSObject对象占16个字节。
那么与题1所述结构体中占8个字节是否冲突?
通过打印NSLog(@"%zd",class_getInstanceSize([NSObject class]))
获取NSObject类的实例对象的成员变量所占用的(内存对齐之后)大小,显示确实为8个字节。
在objc的源码中找到class_getInstanceSize
方法,发现它返回的是cls->alignedInstanceSize()
,对它的描述为Class's ivar size rounded up to a pointer-size boundary意指返回成员变量占据的大小。因此创建一个NSObject对象需要分配16个字节,只是真正利用的只有8个字节,即isa
这个成员的大小。
事实上,查看allocWithZone
的源码发现它最底层的创建实例的方法实际上是调用了C语言的calloc
方法,在该方法中,规定若分配的字节不满16将把它分配为16个字节。
3、若一个Student类继承自NSObject类,那么Student类的对象占多少内存?
分析: 新建Student类,添加成员变量。通过clang反编译,打开cpp文件找到Student类的底层实现。
struct Student IMPL {
struct NSObject_IMPL NSObject_IVARS;
// 还有Student类的成员变量
//……
}
从这段代码可以看出,若一个类继承自另一个类,则它的底层会将父类的成员变量放在结构体的最前面,此后依次放置本类的成员变量。而从之前的分析可知,NSObject_IMPL
的本质就是一个装有成员变量isa
的结构体,因此,Student
类对象所占的内存为isa
的内存8加上Student
类成员变量所占的空间。若不满16个字节,会强制分配到16个字节。另,由于<u>内存对齐</u>的规定,结构体的最终大小必须是最大成员的倍数。
#import <malloc/malloc.h>
@interface Student:NSObject{
@public
int age;
int no;
int address;
NSString *name;
NSString *name2;
}
@end
@implementation Student
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc]init];
NSLog(@"%zd",malloc_size((__bridge const void *)stu));
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
打印结果如下图所示:
结果
但是如果改成如下列代码这样,打印结果却是32
#import <malloc/malloc.h>
@interface Student:NSObject{
@public
int age; // 4
int no; // 4
int address; // 4
}
@end
@implementation Student
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc]init];
NSLog(@"%zd",malloc_size((__bridge const void *)stu));
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
student类的三个成员变量占用内存12,加上父类结构体占用8个字节,共20个。由于内存对齐,取8的倍数为24个。但是通过查询源码可知,分配内存之时,最后调用的是文件stdlib.h的calloc方法。该方法的具体实现如下:
calloc(size_t num_items,size_t size)
{
void *retval;
retval = malloc_zone_calloc(defalt_zone,num_items,size);
if (retval == NULL) {
errno = ENOMEM;
}
return retval;
}
第一个参数代表的是分配几块内存区域,第二个参数代表这块区域的大小该函数会调用malloc_zone_calloc
函数。这个函数有一个Buckets size
的概念。在iOS堆空间分配内存时,分配的内存都是16的倍数。
附:苹果官方开放源码