前言
alloc&init&new都分别做了什么
我们在开发的过程当中 知道是通过这些方法初始化实例 但是并没有关注或研究过他们内部是怎么实现的
准备工具
正文
首先来看一段代码
Person *p1 = [Person alloc];
Person *p2 = [p1 init];
Person *p3 = [p1 init];
NSLog(@"%@-%p-%p",p1,p1,&p1);
NSLog(@"%@-%p-%p",p2,p2,&p2);
NSLog(@"%@-%p-%p",p3,p3,&p3);
打印的东西为对象
,指针地址
,对象地址
输出结果
可以发现输出对象的地址是一样
证明他们指向的是同一块内存地址
alloc原理
接下来我们通过断点去查看具体实现
接下来通过
输入alloc
点击下一个
确定到调用的那个库
调用的这个方法
_objc_rootAlloc
好我们接下来去下载对应的源码
接下我们一步一步的分析
- 1.
alloc
这个我们可以看到里边就调用了一个_objc_rootAlloc方法
+ (id)alloc {
return _objc_rootAlloc(self);
}
- 2.跳转至
_objc_rootAlloc
其中调用callAlloc
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
- 3.跳转至
callAlloc
其中关于 slowpath & fastpath 这里简要说明一下 其实他们都是objc源码当中定义的宏
/////x很可能为真, fastpath 可以简称为
真值判断 其实就是表示这个判断很可能为真 或者表达式成立
#define fastpath(x) (__builtin_expect(bool(x), 1))
///x很可能为假,slowpath 可以简称为
假值判断 其实就是表示这个判断很可能为假 或者表达式不成立
#define slowpath(x) (__builtin_expect(bool(x), 0))
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
其中cls->ISA()->hasCustomAWZ()
是判断一个类是否+allocWithZone
实现
因为这里是模拟器调试所以会执行_objc_rootAllocWithZone
- 5跳转至
_objc_rootAllocWithZone
NEVER_INLINE
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);
}
- 6跳转至
_class_createInstanceFromZone
实现
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
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;
// 1:要开辟多少内存
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 2;怎么去申请内存
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
// 3: ?
if (!zone && fast) {
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);
}
该方法总结为3部分
1.cls->instanceSize(extraBytes)
计算要开辟的内存 以16字节对其
2.(id)calloc(1, size)
开辟内存
3.obj->initIsa(cls)
isa与类关联 初始化isa
根据源码总结得出大致调用流程
根据上边调用路程分析得出有3个方法比较重要
1.cls->instanceSize
计算内存大小
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;
}
流程如下
fastInstanceSize
方法实现
会跳之align16
计算内存大小
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);
}
}
alignedInstanceSize
会跳转至word_align
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
align16
16字节内存对齐方法
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
下边以align16(7)为例
带入公式
22 & ~ 15
0000 0000 0001 0110 //22
0000 0000 0000 1111 //15
1111 1111 1111 0000 //取反
0000 0000 0001 0110 & 1111 1111 1111 0000
0000 0000 0001 0000 //16 最后得到结果16
- 首先将
7 + 15 = 22
- 再将15取反 得到
1111 1111 1111 0000
- 最后将两个结果进行&操作 规则是 1^1=1 其他都是=0得到最后结果 为 16
内存对齐的原因有
- 1.通常内存是由一个个字节组成的,cpu在存取数据时,并不是以字节为单位存储,而是以块为单位存取,块的大小为内存存取力度。频繁存取字节未对齐的数据,会极大降低cpu的性能,所以可以通过减少存取次数来降低cpu的开销
- 2.calloc申请内存返回指针地址
这里我们可以通过断点来印证这一点
这里我们可以看到断点之前是 nil
断点之后返回一个16进制地址
- 3.
obj->initInstanceIsa
类与isa 进行关联
内存也有了 接下来就是该将内存与isa进行关联
这里也可以通过断点来进行验证
在obj->initInstanceIsa
函数调用之后我们可以得到一个结果如下图
总结
- 通过对
alloc
源码的分析得出alloc
主要的作用就是开辟内存 通过16字节对齐 - 主要步骤 计算->开辟->关联
init原理
接下来按照上边alloc的调试方法断点跟进去得到如下两个方法
// Replaced by CF (throws an NSException)
+ (id)init {
return (id)self;
}
- (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 (id)
返回id
类型方便强转
new原理
通过源码可以得到
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
看到这里我们应该就明白 new = alloc + init
在平常开发的过程当中也有用到new
验证
总结
- new最后会调用到init 如果子类重写了init会先调用子类的init
- 如果子类没重写会掉用父类的init
- 如果使用我们alloc+自定义实现方法可以实现更多可能,相比new来说 提供了拓展性 更灵活