一. OC对象的实质是什么
我们可以通过 clang -rewrite-objc main.c -o main.cpp
将OC代码编译成C\C++代码来查看:
// 定义一个Person类
@interface Person : NSObject
{
int _age;
int _height;
NSString *_name;
}
@end
编译过后的核心代码如下:
/// NSObject
struct NSObject_IMPL {
Class isa; // 8字节
};
/// Person
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8字节
int _age; // 4字节
int _height; // 4字节
NSString *_name; // 8字节
};
可以看到, NSObject的类都被编译成了结构体. 并且 NSObject结构体还多了一个isa指针
二. OC类占用多大内存空间
我们可以通过runtime和malloc的库函数打印一下
// 查看类在内存中占用空间
#import <objc/runtime.h>
class_getInstanceSize(Class _Nullable cls)
// 查看对象实际分配的空间
#import <malloc/malloc.h>
malloc_size(const void *ptr)
// 输出结果
NSObject:class_getInstanceSize: 8
NSObject: malloc_size: 16
Person: class_getInstanceSize: 16
Person: malloc_size: 16
首先可以看到, NSObject类 占用空间 8个字节, 通过编译以后的结构体我们也可以知道 一个 isa指针占用8个字节, 同理, Person类 占用 8+4+4+8 = 24个字节可以理解.
那么为什么, 实际分配的地址分别是 16, 32 呢? 我们下边就通过源码查看一下, 为什么
三. alloc 的源码探索
- alloc 函数
//alloc函数
+ (id)alloc {
return _objc_rootAlloc(self);
}
- _objc_rootAlloc 函数
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
- callAlloc函数
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil; // 判断参数, Cls为当前Class checkNil传入的是false
if (fastpath(!cls->ISA()->hasCustomAWZ())) { // 判断是否自定义+allocWithZone实现
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// 判断allocWithZone参数, 上文传入的是 true
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
fastpath 和 slowpath 是2个宏定义, 具体如下:
#define fastpath(x) (__builtin_expect(bool(x), 1))(__builtin_expect(bool(x), 1)) 表示x很可能为真, fastpath 可以简称为 真值判断, 表示if里边的代码执行概率很大
#define slowpath(x) (__builtin_expect(bool(x), 0))(__builtin_expect(bool(x), 0)) 表示x很可能为假, slowpath 可以简称为 假值判断 表示if里边的代码执行概率很小
- _objc_rootAllocWithZone 函数
id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
- _class_createInstanceFromZone 函数
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)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
// 计算需要的空间, extraBytes上一步传入的为0
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 申请空间
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
// 将cls 与 obj 指针关联
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
这个函数是alloc的核心操作, 主要实现了3个任务:
- cls->instanceSize(extraBytes): 计算需要开辟的空间大小
- (id)calloc(1, size): 申请内存, 返回指针地址
- initInstanceIsa(cls, hasCxxDtor): 将类与isa指针关联
- 1.1 instanceSize 计算空间大小的方法
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.
// 最小16字节
if (size < 16) size = 16;
return size;
}
- 1.2 fastInstanceSize和align16, 计算空间大小, 进行16字节对齐
// 计算空间大小
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
// 16字节对齐算法
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
为什么要内存字节对齐:
- CPU在存取数据时, 是以块为单位存取, 可以通过字节对齐, 减少存取次数, 减少CPU的开销
- OC对象的isa指针占用8个字节, 如果没有其他属性时, 使用8字节对齐, 会导致这个对象在内存中与其他对象的isa指针紧挨着, 容易造成访问混乱, 空出8字节可以更安全的访问
字节对齐的规则:
- 每个成员变量起始地址offset要是当前成员所占大小的整数倍
- 如果某个成员变量是结构体, 结构体成员的起始地址offset要以子结构体大小的整数倍
- 结构体的总大小, 必须是内部最大成员的整数倍, 不足的要补齐
- 2 根据计算过后的内存大小, 向内存申请空间,赋值给obj, 此时obj指向该内存地址
- 3 根据申请好的内存地址与当前传入的cls, 将指针和isa 进行关联 通过源码, 我们就知道了为什么 NSObject的实际内存大小是16字节(最小16字节), 以及为什么Person为什么是32字节(16字节对齐)
四. init 源码分析
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}
init 只是将当前self返回到外面
五. new 源码分析
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
可以看到, new 方法直接调用了callAlloc() 方法以后 调用 init, 所以会有 new= alloc + init 的说法
六. calloc源码分析
在objc4的calloc中, 我们就无法在继续深入看到源码, 因为calloc在libmalloc源码中, 下边我们就继续探索源码
- calloc 函数
void * calloc(size_t num_items, size_t size)
{
void *retval;
retval = malloc_zone_calloc(default_zone, num_items, size);
if (retval == NULL) {
errno = ENOMEM;
}
return retval;
}
- malloc_zone_calloc 函数
void * malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
void *ptr;
if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
internal_check();
}
// 在这里设置断点才可以继续调试calloc
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);
}
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
return ptr;
}
- 通过断点, 我们找到calloc实际调用的是default_zone_calloc
static void * default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
zone = runtime_default_zone(); // 创建zone
// 第二个断点
return zone->calloc(zone, num_items, size); // 用zone calloc
}
- runtime_default_zone函数
static inline malloc_zone_t * runtime_default_zone() {
return (lite_zone) ? lite_zone : inline_malloc_default_zone();
}
- inline_malloc_default_zone 函数
static inline malloc_zone_t * inline_malloc_default_zone(void)
{
_malloc_initialize_once();
// malloc_report(ASL_LEVEL_INFO, "In inline_malloc_default_zone with %d %d\n", malloc_num_zones, malloc_has_debug_zone);
return malloc_zones[0];
}
- 回到default_zone_calloc 的第二个断点, 发现实际调用的是nano_calloc函数
static void *
nano_calloc(nanozone_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;
}
if (total_bytes <= NANO_MAX_SIZE) {
// 核心方法
void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
if (p) {
return p;
} else {
/* FALLTHROUGH to helper zone */
}
}
malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
return zone->calloc(zone, 1, total_bytes);
}
- _nano_malloc_check_clear函数简版代码
static void * _nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested)
{
/// ...
// 16位对齐
size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
mag_index_t mag_index = nano_mag_index(nanozone);
nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]);
ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next));
if (ptr) {
/// ....
} else {
// 获取内存指针
ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
}
if (cleared_requested && ptr) {
memset(ptr, 0, slot_bytes); // TODO: Needs a memory barrier after memset to ensure zeroes land first?
}
return ptr;
}
_nano_malloc_check_clear 核心有2个操作
- segregated_size_to_fit(args...):16位对齐
- segregated_next_block(args...): 获取内存指针
-
- segregated_size_to_fit, 通过位运算进行16位对齐
static MALLOC_INLINE size_t segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
size_t k, slot_bytes;
if (0 == size) { // 16倍数对齐
size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
}
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size
*pKey = k - 1;
// k = (size + 15) >> 4 << 4 // Zero-based!
return slot_bytes;
}
-
- segregated_next_block 简版代码
static MALLOC_INLINE void *
segregated_next_block(nanozone_t *nanozone, nano_meta_admin_t pMeta, size_t slot_bytes, unsigned int mag_index)
{
while (1) {
// 当前可用内存的结束地址
uintptr_t theLimit = pMeta->slot_limit_addr; // Capture the slot limit that bounds slot_bump_addr right now
// 偏移到下个地址
uintptr_t b = OSAtomicAdd64Barrier(slot_bytes, (volatile int64_t *)&(pMeta->slot_bump_addr));
// 减去偏移量
b -= slot_bytes; // Atomic op returned addr of *next* free block. Subtract to get addr for *this* allocation.
if (b < theLimit) { // Did we stay within the bound of the present slot allocation?
// 如果当前地址还在范围内, 直接返回
return (void *)b; // Yep, so the slot_bump_addr this thread incremented is good to go
} else {
if (pMeta->slot_exhausted) { // exhausted all the bands availble for this slot?
pMeta->slot_bump_addr = theLimit;
return 0; // We're toast
} else {
// One thread will grow the heap, others will see its been grown and retry allocation
_malloc_lock_lock(&nanozone->band_resupply_lock[mag_index]);
// re-check state now that we've taken the lock
if (false //...)) {
/// ...
} else if (segregated_band_grow(nanozone, pMeta, slot_bytes, mag_index)) {
_malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);
continue; // ... the slot has been successfully grown by us. Now try again.
} else {
/// ...
}
}
}
}
}
-
- segregated_band_grow函数, 开辟新band
static boolean_t
segregated_band_grow(nanozone_t *nanozone, nano_meta_admin_t pMeta, size_t slot_bytes, unsigned int mag_index)
{
/// ...
return TRUE;
}
这部分代码还不是十分理解, 需要进一步解读