OC是面向目标的程序开发言语,目标是它的基本元素,了解目标的本质以及内存分配关于开发者来说是很重要的,能够让一些不可思议的问题不再神秘;在一些面试的时分也会有人常常问到类似的问题,一个最基本的目标占用多少内存空间,一个特定的目标占用多少空间,咱们今天的任务也便是聊一聊这两个问题,把这两个问题解决了,iOS目标的也就基本了解的差不多了
NSObject的内存巨细
咱们先来看一下一个最基本的NSObject
占用多大的内存空间,先看一下下面的代码来打印NSObject
目标的巨细
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
NSObject *obj = [[NSObject alloc] init];
NSLog(@"%zd ", malloc_size((__bridge const void *)(obj)));
NSLog(@"%zd ", class_getInstanceSize([NSObject class]));
return 0;
}
打印成果
2023-02-01 15:46:32.041969+0800 TestOC[83749:7940065] 16
2023-02-01 15:46:32.043200+0800 TestOC[83749:7940065] 8
从成果来看,咱们应该有两个疑问?
- 为什么两个获取目标巨细的函数
malloc_size
和class_getInstanceSize
获取同一个目标的内存巨细不一样呢?-
malloc_size
回来体系实践分配的内存巨细 -
class_getInstanceSize
回来实践需求分配的内存巨细
-
- 为什么实践需求分配的巨细是
8
,而体系分配巨细是16
呢?-
class_getInstanceSize
巨细为8
?- 涉及到
NSObject
的底层结构以及class_getInstanceSize
的内部完成
- 涉及到
-
malloc_size
回来16
?- 看
alloc
分配内存巨细流程就知道了
- 看
-
class_getInstanceSize
巨细为8
?
NSObject的底层结构
从体系文件剖析
咱们能够看下体系中对NSObject的界说,位于 usr/include/objc/NSObject.h
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
+ (void)load;
+ (void)initialize;
// ...其他办法
@end
从结构上看NSObject
只要一个Class isa
变量,那Class
又是个啥呢?看下面的界说,位于 usr/include/objc/objc.h
,它是一个 struct objc_class *
类型的指针
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
从生成的源码剖析
对上面的代码履行指令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
,在生成的main.cpp中能够看到下面的代码
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
总结
NSObject
底层是一个结构体,内部只要一个 Class
类型的 isa
变量,Class
是一个struct objc_class *
类型的指针,也便是说NSObject
内部只要一个isa
指针变量
class_getInstanceSize 的内部完成
这个办法的内部完成需求下载objc源码 opensource.apple.com/tarballs/ob…
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() const {
return word_align(unalignedInstanceSize());
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
从完成能够看出,class_getInstanceSize
内部是回来了内存对齐之后的目标巨细
关于内存对齐的含义能够自行google,或参考下面的内存对齐,有两条规矩:
- 数据成员对齐规矩:
struct
或union
的数据成员,第一个数据成员放在offset为0
的地方,今后每个数据成员的对齐依照#pragma pack
指定的数值和这个数据成员自身长度中,比较小的那个进行- 结构(或联合)的整体对齐规矩:在数据成员完成各自对齐之后,
struct
或union
本身也要进行对齐,对齐将依照#pragma pack
指定的数值和struct
或union
最大数据成员长度中,比较小的那个进行。iOS 中 #pragma pack 默许是8
在iOS
的64位体系中,一个指针占用8
个字节,根据内存对齐规矩,所以 class_getInstanceSize([NSObject class])
回来8
malloc_size
回来16
?
malloc_size
获取的是实践分配的内存空间巨细,每个目标为何占用这么多内存空间,这是由 alloc
函数来决议的,下面咱们就来深化alloc
函数内一探终究
+ (id)alloc {
return _objc_rootAlloc(self);
}
id _objc_rootAlloc(Class cls) {
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
// ... 其他与内存分配无关代码省掉
}
id _objc_rootAllocWithZone(Class cls, objc_zone_t zone __unused) {
return _class_createInstanceFromZone(cls, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC);
}
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil) {
// ... 其他与内存分配无关代码省掉
size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
}
// ... 其他与内存分配无关代码省掉
}
alloc
终究是由 _class_createInstanceFromZone
内调用 instanceSize()
的来核算需求的内存。由完成可知,如果传入的值小于16的话,直接回来16,也便是注释上说的:CF(CoreFoundation)要求一切的目标至少16个字节
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
也便是说在创建NSObject
的时分需求8个字节(Class isa
指针只占8个字节巨细),可是因为小于16
,所以分配了16
个字节,所以 malloc_size
回来16
iOS 目标的内存
关于自界说的iOS
目标的内存分配,此处以Person
为例,有两个成员变量,age、height
,代码如下
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface Person : NSObject {
@public
int age;
int height;
}
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
Person *person = [[Person alloc] init];
person->age = 20;
person->height = 10;
NSLog(@"%zd ", malloc_size((__bridge const void *)(person)));
NSLog(@"%zd ", class_getInstanceSize([Person class]));
return 0;
}
能够看到成果 malloc_size
和 class_getInstanceSize
的成果都是 16
剖析如下:
运用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
指令检查一下 Person
的底层结构
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int age;
int height;
};
// 等同于
struct Person_IMPL {
Class isa; // 8字节
int age; // 4字节
int height; // 4字节
};
// 一共16个字节
依照之前对malloc_size
和 class_getInstanceSize
的探究可知成果是16
是正确的
检查 person
目标的内存散布
在 return 0;
处断点,在控制台打印 person
的地址,经过 Debug => Debug Workflow => View Memory
检查 person
目标的内存散布;也能够运用 x person
直接打印出内存散布,参考如下
iOS关于目标的内存分配也有一些优化,参考 /post/684490…
iOS底层内存分配函数 你不知道的细节(可选)
经过上面的剖析,在创建目标的时分会调用alloc
函数,内部会调用到_class_createInstanceFromZone
,它经过instanceSize
函数核算需求分配的内存巨细,终究调用malloc_zone_calloc
函数来分配内存。但实践上malloc_zone_calloc
也有它自己的内部完成。
源码下载 github.com/apple-oss-d…
在源码中查找 malloc_zone_calloc
能得到下面的成果
void * malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size) {
return _malloc_zone_calloc(zone, num_items, size, MZ_NONE);
}
static void * _malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
malloc_zone_options_t mzo) {
if (zone == default_zone && !lite_zone) {
zone = malloc_zones[0];
}
if (os_unlikely(malloc_instrumented || malloc_check_start ||
malloc_logger || zone->version < 13)) {
return _malloc_zone_calloc_instrumented_or_legacy(zone, num_items, size, mzo);
}
return zone->calloc(zone, num_items, size);
}
分配内存空间涉及到 malloc_zone_t
以及它的 calloc
(zone->calloc(zone, num_items, size);
) 函数,并且有 version
字段的判别(zone->version < 13
)
找到 malloc_zone_t
的界说
typedef struct _malloc_zone_t {
/* Only zone implementors should depend on the layout of this structure;
Regular callers should use the access functions below */
void *reserved1; /* RESERVED FOR CFAllocator DO NOT USE */
void *reserved2; /* RESERVED FOR CFAllocator DO NOT USE */
size_t (* MALLOC_ZONE_FN_PTR(size))(struct _malloc_zone_t *zone, const void *ptr); /* returns the size of a block or 0 if not in this zone; must be fast, especially for negative answers */
void *(* MALLOC_ZONE_FN_PTR(malloc))(struct _malloc_zone_t *zone, size_t size);
void *(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */
void *(* MALLOC_ZONE_FN_PTR(valloc))(struct _malloc_zone_t *zone, size_t size); /* same as malloc, but block returned is set to zero and is guaranteed to be page aligned */
void (* MALLOC_ZONE_FN_PTR(free))(struct _malloc_zone_t *zone, void *ptr);
void *(* MALLOC_ZONE_FN_PTR(realloc))(struct _malloc_zone_t *zone, void *ptr, size_t size);
void (* MALLOC_ZONE_FN_PTR(destroy))(struct _malloc_zone_t *zone); /* zone is destroyed and all memory reclaimed */
const char *zone_name;
// ... 其他可选办法 略
unsigned version;
} malloc_zone_t;
这里的注释说的很清楚 Only zone implementors should depend on the layout of this structure
(只要完成者应该依赖于这个结构的布局)也便是说这相当于一个抽象类,需求其他类去完成它
下面咱们查找 calloc =
或许 version =
,(因为能够直接赋值的方式给结构体的变量赋值)终究确认是下面的查找成果
nanozone->basic_zone.calloc = OS_RESOLVED_VARIANT_ADDR(nanov2_calloc);
找到对应的函数 nanov2_calloc
MALLOC_NOEXPORT void * nanov2_calloc(nanozonev2_t *nanozone, size_t num_items, size_t size)
{
size_t total_bytes;
if (calloc_get_size(num_items, size, 0, &total_bytes)) {
return NULL;
}
// 核算需求的巨细
size_t rounded_size = _nano_common_good_size(total_bytes);
// 分配小目标时 运用 nanozonev2_t 类型的 zone
if (total_bytes <= NANO_MAX_SIZE) {
// 详细的分配细节... 略
}
// Too big for nano, so delegate to the helper zone.
// 分配大目标时 运用 create_scalable_szone 创建的 szone_t 类型的zone
return nanozone->helper_zone->calloc(nanozone->helper_zone, 1, total_bytes);
}
nanov2_calloc
内部是经过 _nano_common_good_size
来核算内存分配的,详细完成如下
#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ..., 256} */
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
static MALLOC_INLINE size_t _nano_common_good_size(size_t size) {
return (size <= NANO_REGIME_QUANTA_SIZE) ? NANO_REGIME_QUANTA_SIZE
: (((size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM) << SHIFT_NANO_QUANTUM);
}
_nano_common_good_size
函数简单了解便是:根据入参size
巨细,如果小于16,就回来16,否则回来 16 的倍数。也便是说 _nano_common_good_size
回来的值一定是 16 * n
(n>0)
总结:[NSObject alloc]
终究分配的巨细由 _nano_common_good_size()
函数决议的,且巨细必定是 16 的倍数。
这样就好了解下面的一段代码了
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface Person : NSObject {
@public
int age;
int height;
int score;
}
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
Person *person = [[Person alloc] init];
NSLog(@"%zd ", malloc_size((__bridge const void *)(person)));
NSLog(@"%zd ", class_getInstanceSize([Person class]));
return 0;
}
成果打印
2023-02-02 00:24:45.815252+0800 TestOC[33836:8445818] 32
2023-02-02 00:24:45.815855+0800 TestOC[33836:8445818] 24
成果剖析:Person
的底层结构体依照之前的规律,应该是
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int age;
int height;
int score;
};
// 等同于
struct Person_IMPL {
Class isa; // 8字节
int age; // 4字节
int height; // 4字节
int score; // 4字节
};
// 一共20个字节,对齐之后是24个字节
Person
底层需求24 个字节,可是 alloc
函数终究调用的 _nano_common_good_size()
函数回来的是16的倍数,所以核算后回来32