要看流程肯定要看源码,可以下载objc4最新的781版本,参考这里自己配置成可编译状态,也可以直接下载已经编译好的。
下载完成之后,创建自己的target
image.png
然后选择
image.png
创建完成之后,要修改下面的配置否则,断点不能跟进源码
image.png image.png
1. alloc 流程
进过上面的准备工作,现在就可以轻松探索alloc流程了。
1.1 alloc做了什么事情
创建一个MlqqObject继承自NSObject,然后调用alloc方法,然后打印一下
image.png 从打印信息可以看到,有MlqqObject
这个类名,还有一个地址0x10069ebf0
,有地址说明向系统申请了空间,有空间就有大小,这是从打印信息里面可以看出来的,究竟是不是,我们往下看。
1.2 断点跟踪
image.png跟断点进来
_objc_rootAlloc
这个方法,跟进去_objc_rootAlloc
image.png
_objc_rootAlloc
这个方法里面调用了callAlloc
,继续跟进去callAlloc
image.png
callAlloc
里面调用_objc_rootAllocWithZone
继续跟进_objc_rootAllocWithZone
这个方法里面还有一行代码return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
这个方法也会执行,因为他是在后面并不是在#if 与 #endif
之间,但是这里为什么断在_objc_rootAllocWithZone
这里呢,这个问题在后面解释,先把流程走完。
_objc_rootAllocWithZone
里面调用_class_createInstanceFromZone
方法,继续跟_class_createInstanceFromZone
image.png
终于在_class_createInstanceFromZone
方法里面看到return
了,在此之前的所有调用方面里面都是直接
return
调用方法的返回值,所以_class_createInstanceFromZone
才是我们研究的重点,下面是_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;
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) {
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);
}
在这个方法里面我们关注三行代码
size = cls->instanceSize(extraBytes);
obj = (id)calloc(1, size);
obj->initInstanceIsa(cls, hasCxxDtor);
从这三个方法的名字可以看出三个方法的作用分别是计算类所需要的大小、开辟空间、初始化isa(也就是说将类与开辟的空间绑定)。
关于计算类所需空间的大小、开辟空间、初始化isa三个部分,每个部分涉及的知识点都很多,所以分了三篇文章详细说明
- alloc总结: image.png
1.3 callAlloc 方法分析
在上面我们看了 calloc
里面是走到了_objc_rootAllocWithZone
里面,那么再看下面:
objc_msgSend
方法,而且并不是从_objc_rootAlloc
进来的,而是从objc_alloc
进来的?
其实上面总结的alloc
流程是在这之后的,也就是说calloc方法会走两遍,第一遍从objc_alloc
进来,就是我们现在看到的情况,向cls
发送alloc
消息之后才会真正开始调用+ (id)alloc
方法
为什么走两遍,两遍却调用不同的方法呢,我们来分析一下calloc
的源码:
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));
}
(1)if (slowpath(checkNil && !cls))
因为cls不可能为空 所以 这个if
不会执行
(2)if (fastpath(!cls->ISA()->hasCustomAWZ()))
这个分支主要是hasCustomAWZ()
这个方法的值,这个方法不知道是什么意思,AWZ
就是allocWithZone
的简写,这个方法就是判断有没有自定义的allocWithZone
方法的。
为什么有这个判断,看一下苹果官方的关于allocWithZone
的文档:
看一下hasCustomAWZ()
的源码:
bool hasCustomAWZ() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
翻译一下这个方法:不是默认的allocWithZone
就是有自定义的allocWithZone
第一次hasCustomAWZ()
返回为false
,说明还没有设置为默认的allocWithZone
,那是什么时候设置的呢,就在hasCustomAWZ()
的下面就是setHasDefaultAWZ()
方法:
void setHasDefaultAWZ() {
cache.setBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
可以在这个打个断点,可以看到:
image.png 可以从调用堆栈中看到正是在调了objc_msgSend
之后,调setHasDefaultAWZ()
方法,这个方法是在_finishInitializing
方法之后,也就是说只要调了+ (id)initialize
方法,系统就会调setHasDefaultAWZ()
设置是否有自定义的allocWithZone
方法,而对cls
发送alloc
消息正好先触发+ (id)initialize
方法,等调用alloc
->_objc_rootAlloc
->callAlloc
这个流程(也就是第二遍)setHasDefaultAWZ()
已经被调用,所以就可以判断出来有没有自定义的allocWithZone
方法。为了验证是不是这样,在
MlqqObject
类加下面代码:
image.png
上面代码的作用是类被加载的时候就调一下+ (id)initialize
方法:再次运行: image.png 可以看到即使是从
objc_alloc
进来的也会调用_objc_rootAllocWithZone
方法。所以calloc
方法只会走一遍。
那我们在MlqqObject
类里面重写allocWithZone
方法
allocWithZone
方法,hasCustomAWZ()
返回为false
,而
id objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
第三个参数为false
,所以 if (allocWithZone)
不会走, 所以又走到了objc_msgSend
方法,接下来又是alloc
->_objc_rootAlloc
->callAlloc
这个流程,而
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
第三个参数为true
,所以 if (allocWithZone)
会走
2 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
方法什么也没做,只是提供了一个方法供开发者去重写。
3 new方法
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
new
方法调了两个方法callAlloc
和init
。
(1) callAlloc
跟alloc
流程里面是一样的,只不过第三个参数为false
,所以不支持自定义的allocWithZone
方法。
(2)默认调用的是init
方法,如果自己重写了init
方法,会调用自己重写的,如果没有重写就会调用默认的。如果自定了其他的init
方法,比如说initWithString
,别人如果使用new
初始化的时候是不会调用的。
所以基于以上两点,不建议使用new
方法,不过如果不存在上面两种情况,使用new
,也是没有问题的。
网友评论