1. 要研究Objective-C对象的本质,针对NSObject进行一探究竟
int main(int argc,const char* argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
}
return 0;
}
终端执行这段代码将.m文件转换成成c++文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-cpp.cpp
屏幕快照 2018-07-31 下午1.39.34.png
//在cpp文件中找到main函数的实现:如下
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;
}
//这是Class的定义, 可以看到这是一个指向结构体的指针
typedef struct objc_class *Class;
//这是转成C++语言的结构体
struct NSObject_IMPL {
Class isa;
};
//这是Xcode里面点击NSObject 直接看到的声明
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
从而得出屏幕快照 2018-07-31 下午2.53.55.png
可以看到NSObject类的本质是结构体
拥有一个指向typedef struct objc_class 的结构体指针
2: NSObject所占内存大小
我们可以看到NSObject内部就只有一个成员变量isa指针
一个指针在系统中所占用内存大小为:
指针变量在内存中所占空间的大小与变量类型并没有关系,只与操作系统位数有关,64位系统占8字节,32位系统占4字节。
苹果官方资料宣布iOS7.x的SDK支持了64位的应用,而且内置的应用都已经是64位。
所以当前得出指针大小为8位
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
//sizeof 是一个运算符
NSLog(@"sizeof - %zd",sizeof(obj));
//获取类的实例对象的成员变量所占用的大小
NSLog(@"class_getInstanceSize - %zd",class_getInstanceSize([NSObject class]));
//获取指针指向的内存大小
NSLog(@"malloc_size - %zd",malloc_size((__bridge const void *)(obj)));
}
return 0;
}
//运行之后打印信息如下:
2018-07-31 15:25:48.385385+0800 oc[54118:2818629] sizeof - 8
2018-07-31 15:25:48.385668+0800 oc[54118:2818629] class_getInstanceSize - 8
2018-07-31 15:25:48.385703+0800 oc[54118:2818629] malloc_size - 16
Program ended with exit code: 0
可以看出指针指向的内存大小为16个字节
分析一下:
因为Objective-C的对象本质是结构体, 但是结构体拥有自己的内存对齐
结构体内存对齐:
因为为了CPU能够快速访问,提高访问效率,变量的起始地址应该具有某些特性,这就是所谓的“对齐”。比如4字节的int型变量,那它的起始地址就应该在4字节的边界上,即起始地址可以被4整除。内存对齐的规则很简单:
- 起始地址为该变量类型所占内存的整数倍,若不足则不足部分用数据填充至所占内存的整数倍。
- 该结构体所占总内存为结构体成员变量中最大数据类型的整数倍。
根据结构体内存对齐我们可以计算出8个字节是符合内存对齐的
**???那么为什么是16个字节呢? **
在看源码中,CoreFoundation中硬性规定分配内存为16
屏幕快照 2018-07-31 下午3.57.28.png
那么可以得出一个NSObject对象占用的内存为16个字节, 其中有8个字节保存着isa指针, 剩余8个为多出来的内存空间
3. 看下一个问题
@interface Cat : NSObject
{
int _age;
int _weight;
int _height;
}
@end
@implementation Cat
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
//sizeof 是一个运算符
NSLog(@"sizeof - %zd",sizeof(cat));
//获取类的实例对象的成员变量所占用的大小
NSLog(@"class_getInstanceSize - %zd",class_getInstanceSize([Cat class]));
//获取指针指向的内存大小
NSLog(@"malloc_size - %zd",malloc_size((__bridge const void *)(cat)));
}
return 0;
}
我们按照上面说的研究下这个cat所占用内存大小
cat这个对象所拥有成员变量有,我们用clang一下看一下
struct NSObject_IMPL {
Class isa;
};
struct Cat_IMPL {
struct NSObject_IMPL NSObject_IVARS;//父类结构体
int _age;
int _weight;
int _height;
};
这样看下来cat实例对象所拥有成员变量有
- Class isa; 占用8个字节
- int _age;占用4个字节
- int _weight;占用4个字节
- int _height;占用4个字节
这样计算下来有20个字节, 按照结构体内存对齐,那么我们计算下来是24个字节.那我们来看一下log
2018-07-31 16:41:24.479647+0800 oc[54772:2907708] sizeof - 8
2018-07-31 16:41:24.479959+0800 oc[54772:2907708] class_getInstanceSize - 24
2018-07-31 16:41:24.479989+0800 oc[54772:2907708] malloc_size - 32
Program ended with exit code: 0
可以看到是32个字节, 那我们看一下objc的源码,探究下为什么会是分配了32个字节内存
屏幕快照 2018-07-31 下午3.47.05.png
//其中这两句代码
extraBytes这个值传过来的时候为0
size_t size = cls->instanceSize(extraBytes);
obj = (id)calloc(1, size);
//解释 cls->instanceSize(extraBytes)
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
//alignedInstanceSize() 这个函数
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
//解释alignedInstanceSize()
class_getInstanceSize(Class)这个函数也调用者个函数
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
我们刚才打印出来class_getInstanceSize([Cat class])是24,也就是说是obj = (id)calloc(1, size);
这句代码使内存从24变成了32,
void *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);
calloc() 这个函数声明在stdlib库里面,那我们找到这个库的源码看一下
void *
calloc(size_t num_items, size_t size)
{
void *retval;
//发现调动了malloc_zone_calloc这个函数
retval = malloc_zone_calloc(default_zone, num_items, size);
if (retval == NULL) {
errno = ENOMEM;
}
return retval;
}
malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
void *ptr;
size_t alloc_size;
if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
internal_check();
}
if (os_mul_overflow(num_items, size, &alloc_size) || alloc_size > MALLOC_ABSOLUTE_MAX_SIZE){
errno = ENOMEM;
return NULL;
}
ptr = zone->calloc(zone, num_items, size);
if (malloc_logger) {
malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
}
return ptr;
}
这一段代码过于复杂了,看这个宏定义
#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */
我们在像iOS操作系统申请内存的时候,操作系统也有自己的一套内存对齐, 操作系统有最优的分配内存方式, 是按照16的倍数去进行分配内存.
4: 还有一种情况,直接看代码
@interface Animal : NSObject
{
int _name;
}
@end
@implementation Animal
@end
@interface Cat : Animal
{
int _weight;
}
@end
@implementation Cat
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
//sizeof 是一个运算符
NSLog(@"sizeof - %zd",sizeof(cat));
//获取类的实例对象的成员变量所占用的大小
NSLog(@"class_getInstanceSize - %zd",class_getInstanceSize([Cat class]));
//获取指针指向的内存大小
NSLog(@"malloc_size - %zd",malloc_size((__bridge const void *)(cat)));
}
return 0;
}
我们来分析下cat这个对象系统分配了多少内存,
这里分析的是结构体所占用内存
struct NSObject_IMPL {
Class isa; //8个字节
};
struct Animal_IMPL {
struct NSObject_IMPL NSObject_IVARS; //8个字节
int _name; //4个字节
}; // 结构体内存对齐 这个结构体为 16个字节
struct Cat_IMPL {
struct Animal_IMPL Animal_IVARS;//16个字节
int _weight; //4个字节
};// 结构体内存对齐 这个结构体为 32个字节
我们看一下结果
2018-07-31 17:29:49.638592+0800 oc[55144:2974294] sizeof - 8
2018-07-31 17:29:49.638833+0800 oc[55144:2974294] class_getInstanceSize - 16
2018-07-31 17:29:49.638856+0800 oc[55144:2974294] malloc_size - 16
Program ended with exit code: 0
我们看到我们又错了, 系统分配了16个字节, 并不是我们分析的32个字节,我们看一下系统分配的空间都都放置了哪些数据
系统分配内存时候,其中成员变量的内存块是连续的, 那我们从isa分配了8个是0x000000b1-0x000000b8, _name是从0x000000b9-0x000000bc, _weight是0x000000bd-0x000000c0
-
可以看到系统分配内存会看该类的成员占用内存最优分配内存
网友评论