alloc对象源码分析-下篇

235 阅读4分钟

接着上篇的第三步我们继续内存分析

alloc对象源码分析-上篇

上篇研究到了不同类型在内容中所占用的size。我们接着第三步继续开始,我们先看两个struct

struct LGStruct1 {
    double a;       // 8    [0 7]
    char b;         // 1    [8]
    int c;          // 4    (9 10 11 [12 13 14 15]
    short d;        // 2    [16 17] 24
}struct1;

struct LGStruct2 {
    double a;       // 8    [0 7]
    int b;          // 4    [8 9 10 11]
    char c;         // 1    [12]
    short d;        // 2    (13 [14 15] 16
}struct2;

struct LGStruct3 {
    double a;       // 8    [0 7]
    int b;          // 4    [8 9 10 11]
    char c;         // 1    [12]
    short d;        // 2    (13 [14 15] 
    int e;          // 4    [16 17 18 19]
    struct LGStruct1 str;   //24  (20 21 22 23 [24...46]  48
}struct3;

NSLog(@"%lu-%lu-%lu",sizeof(struct1),sizeof(struct2),sizeof(struct3));

**2021-09-02 09:13:47.620830+0800 001-内存对齐原则[20249:1936263] 24-16-48**

从打印结果上发现结构体里面的属性个数和类型是一样的,但是结构体的size却不一样。

总结:
  1. 属性是按照size的倍数位置进行存储,cpu按照整数倍进行读取,空间换时间,速度更快
  2. 对象struct是按照16字节进行对齐

再回到我们的对象中,给person增加几个属性

    LGPerson *person = [LGPerson alloc];
    person.name      = @"stone";
    person.nickName  = @"ios";
    person.age       = 18;
    person.height    = 190.5;
    person.c1        = 'a';
    person.c2        = 'b';
    NSLog(@"对象类型的内存大小--%lu",sizeof(person));
    NSLog(@"对象实际的内存大小--%lu",class_getInstanceSize([person class]));
    NSLog(@"系统分配的内存大小--%lu",malloc_size((__bridge const void *)(person)));
    
    2021-09-02 09:43:11.015921+0800 001-内存对齐原则[20462:1970436] 对象类型的内存大小--8
    2021-09-02 09:43:11.016650+0800 001-内存对齐原则[20462:1970436] 对象实际的内存大小--40
    2021-09-02 09:43:11.016747+0800 001-内存对齐原则[20462:1970436] 系统分配的内存大小--48

截屏2021-09-02 上午9.38.26.png

可以发现苹果对于属性在存储的时候,做了一些优化,将int char公用了8个字节的内存。

再研究一下class_getInstanceSize函数是怎么计算对象的size的。依然是看objc4-818.2源码,追进去看到这个方法

 uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
    }
 static inline size_t word_align(size_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
    }  

class_getInstanceSize在计算好对象空间后,做了一步(x + WORD_MASK) & ~WORD_MASK 8字节对齐的操作,和对象alloc的时候,做的16字节对齐是一样的

malloc_size是返回系统给对象开辟的内存,就alloc方法入手找打calloc函数

calloc(size_t num_items, size_t size)
{
	return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}

_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
		malloc_zone_options_t mzo)
{
	MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);

	void *ptr;
	if (malloc_check_start) {
		internal_check();
	}
        //这是关键,别问,问就是一下就看到了
	ptr = zone->calloc(zone, num_items, size);

	if (os_unlikely(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);
	if (os_unlikely(ptr == NULL)) {
		malloc_set_errno_fast(mzo, ENOMEM);
	}
	return ptr;
}

发现one->calloc(zone, num_items, size);但是点不进去了,通过汇编的方式继续往下走。

截屏2021-09-02 上午10.20.48.png

就找到了关键函数

#define SHIFT_NANO_QUANTUM		4
#define NANO_REGIME_QUANTA_SIZE	(1 << SHIFT_NANO_QUANTUM)	// 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) {
		size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
	}
        //k = (size + 16 - 1) >> 4 左移4位
	k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM;
        // round up and shift for number of quanta
        // slot_bytes = k << 4   右移4位
	slot_bytes = k << SHIFT_NANO_QUANTUM;	
        // multiply by power of two quanta size
	*pKey = k - 1;					
        // Zero-based!
	return slot_bytes;
}

结论:最后在创建对象的时候,苹果是按照16个字节进行对齐的。

第四步 对象的本质

使用clang编译器,将oc变成c++文件,看一下发生了什么

clang -rewrite-objc main.m -o main.cpp 
//UIKit报错
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
// xcrun命令基于clang基础上进行了封装更好用
//3、模拟器编译
 xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 
//4、真机编译
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 

clang编译后,我们看到对象就是结构体

#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif

struct NSObject_IMPL {
	Class isa;
};

extern "C" unsigned long OBJC_IVAR_$_LWPerson$_LWname;
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_age;
struct LWPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	double  height;
	NSString *_LWname;
	NSInteger _age;
};

// @property(nonatomic, copy)NSString *LWname;
// @property(nonatomic,assign)NSInteger age;
/* @end */

// @implementation LWPerson

struct NSObject_IMPL {
    Class isa;
};
  • Class类型实际是一个 objc_class类型的结构体指针,objc_class是所有类的底层实现。由此我们猜测isa可能跟类信息存在着重要的关联,具体的关联下面会进行探究。

  • NSObject的底层实现和对象的底层实现有什么区别。两者成员变量的结构体都是Class isa,那么就必然存在继承关系。所有对象的底层都是继承objc_object,在OC中基本上所有的对象都是继承NSObject,但是真正的底层实现是objc_object的结构体类型。

对象的本质就是一个结构体,对象内的isa指向object的isa

最后再附上两张流程图,用于继续研究

instanceSize流程.png

isa流程图.png