本文只做个人笔记使用,引用部分会在文末著名来源
alloc+inti整体流程:
alloc核心三步:
1.先计算出需要的内存大小空间;
2.向系统申请开辟内存,返回地址指针;
3.将cls类与obj指针关联;
在instanceSize
的源码中实现内存大小
size_t instanceSize(size_t extraBytes) const {
//编译器快速计算内存大小
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
// 计算类中所有属性的大小 + 额外的字节数0
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
//如果size 小于 16,最小取16
if (size < 16) size = 16;
return size;
}
通过断点调试,会执行cache.fastInstanceSize
方法,快速计算内存大小
在fastInstanceSize
源码中,会执行到align16
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
//Gcc的内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0
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
//删除由setFastInstanceSize添加的FAST_CACHE_ALLOC_DELTA16 8个字节
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
在align16
的源码中,这个方法是16字节对齐算法
//16字节对齐算法
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
内存对齐原则:
1:数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第
⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存储。 min(当前开始的位置m n) m = 9 n = 4 9 10 11 12
2:结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3:收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,必须是其内部最⼤
成员的整数倍,不⾜的要补⻬。
为什么需要16字节对齐
需要字节对齐的原因,有以下几点:
通常内存是由一个个字节组成的,cpu在存取数据时,并不是以字节为单位存储,而是以块为单位存取,块的大小为内存存取力度。频繁存取字节未对齐的数据,会极大降低cpu的性能,所以可以通过减少存取次数来降低cpu的开销
16字节对齐,是由于在一个对象中,第一个属性isa占8字节,当然一个对象肯定还有其他属性,当无属性时,会预留8字节,即16字节对齐,如果不预留,相当于这个对象的isa和其他对象的isa紧挨着,容易造成访问混乱
16字节对齐后,可以加快CPU读取速度,同时使访问更安全,不会产生访问混乱的情况
字节对齐-总结
在字节对齐算法中,对齐的主要是对象,而对象的本质则是一个 struct objc_object的结构体,
结构体在内存中是连续存放的,所以可以利用这点对结构体进行强转。
苹果早期是8字节对齐,现在是16字节对齐。
总结:
1.通过对alloc
源码的分析可以得知alloc的只要目的就是开辟内存
,而且开辟的内存需要使用16字节对齐的算法
,现在开辟的内存基本上都是16的整数倍;
2.开辟内存的核心步骤有三步:计算内存大小-申请内存-将isa与cls关联
init源码探索
类方法init
+ (id)init {
return (id)self;
}
这里的init是一个构造方法 ,是通过工厂设计(工厂方法模式),主要是用于给用户提供构造方法入口。这里能使用id强转的原因,主要还是因为 内存字节对齐后,可以使用类型强转为你所需的类型
实例方法init
- (id) init {
return _objc_rootInit(self);
}
进入_objc_rootInit源码
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;
}
返回的是传入的 self
本身
new源码
一般在开发中,初始化除了init
,还可以使用new
,两者本质上并没有什么区别,以下是objc中new的源码实现,通过源码可以得知,new函数中直接调用了callAlloc
函数(即alloc中分析的函数),且调用了init函数,所以可以得出new 其实就等价于 [alloc init]的结论
.
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
一般开发中并不建议使用new,主要是因为有时会重写init方法做一些自定义的操作,例如 initWithXXX
,会在这个方法中调用[super init]
,用new初始化可能会无法走到自定义的initWithXXX
部分。