一、序言
在iOS开发过程中的几年中,很多东西都没有静下来好好研究一下,就如一些简单的东西,而是只是按部就班的知道大家都是这样使用的,所以也就囫囵吞枣的使用着,具体是如何实现的,究竟使用了一些什么东西,自己也是一头雾水,如今进入直播课堂,根据课堂布置的作业,自己才有机会去慢慢研究一些简单的底层实现,
比如就连我们经常使用的创建一个对象的简单的不能再简单的事,如果要真的问,
-
alloc 底层都做了什么?
-
内存分配是怎么样的?
-
怎么就能创建一个你想要的类对象示例?
-
在内存分配后如何关联你创建的对象的?
这一系列的问题对我自己来说肯定是一问三不知,所以今天自己去结合项目和老师讲解的内容,自行整理一些知识点,大神略过。本文是自我学习总结的一个过程,而不是为了技术分享。希望在茫茫的知识海洋中追求自己想要的一点小进步。
二,alloc 分配过程都做了些什么,
我们利用项目中的创建一般对象,断点调试能发现,
,
通过一系列的符号断点调试,我们能发现alloc 底层的调用过程有
-
_objc_rootAlloc
-
calloc
-
objc_msgSend
-
_objc_rootAllocWithZone
-
_class_createInstanceFromZone
其中 _class_createInstanceFromZone 又分为三步
1, cls ->InstanceSize //计算所需内存大小
2, calloc, //,根据所需内存大小进行系统分配
3,objc ->InitInstanceIsa //分配了内存后和相关的类进行关联对象
大致流程图如下,图片来源于逻辑教育金牌讲师---那个最帅的男人Cooci老师PPT 截图
接下来我们逐一的进行源码分析,解析alloc 的分配流程:
step1:_objc_rootAlloc
首先根据断点进入到alloc 的断点,我们进入到的代码是
+ (id)alloc {
return _objc_rootAlloc(self);
}
在此step into ,能看到我们的第一步调用的是 _objc_rootAlloc(self);我们再次进入源码到
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
第一步我们看到的只是一个简单的调用系统方法,没有太多的问题,所以相信大家都明白
step2: callAlloc
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));
}
第二步主要是进行条件的判断,当然最下边的那部分判断一般情况下是不会走的,所以也没什么大的作用,因为allocWithZone这是苹果早起的内存分配情况,现在都不怎么使用此方法进行内存分配了,除非系统比较老的版本可能有用。
首先进行判空处理,在判断类对象是否为空,如果成立,则直接返回空,不进行创建,否则判断类对象的ISA对象内存分配有无情况
bool hasCustomAWZ() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
如果没有,分配,才调用step3,_objc_rootAllocWithZone(cls, nil);
objc_msgSend 再此处的调用是转换为系统的一系列方法调用,详细了解RunTime 的开发者都知道在系统的任何调用方法,都会转换为底层的方法转发,通过ISA找到相应的函数实现IMP,所以此处并非是一个具体的方法,而是一系列的系统函数调用,所以没有断点进入到具体的某一个IMP。
step3: _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);
}
第三步直接调用了step4的方法,也是alloc最重要的逻辑存在;
step4:_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;
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) {
3:根据分配的内存情况管理对象
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);
}
次步骤就是iOS的对象创建核心内容,其中包括了三个步骤,我已经在代码中标注了1、2、3的地方,接下来我们对里边的具体实现进行一个详细的分析。我们顺着代码进入到第一步
-
步骤1:计算相关内存大小
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;
}
根据断点我们能进入到 return cache.fastInstanceSize(extraBytes);
这里,再次进入代码到
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);
}
}
最后通过一个计算得到是一个align16
的数据返回类型,第一段代码告诉我们,系统是通过16字节对齐的方式,如果分配小于16字节,就直接等于16字节。所以此时的size 是16,
align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
- size : 16字节对齐,此时是16,
- extra :是系统计算的额外信息,根据断点此时是0,
-
FAST_CACHE_ALLOC_DELTA16:是宏定义 :此时是8
所以结合最终返回的align16
的值是 align16(8)
我们在此进入align16
的定义函数
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
此处代码的意思就是,上一步计算结果 的值加上15,和15的值取反过后再进行 按位与
操作,
什么意思呢:例如上一步我们计算的结果是8,再加上15 结果等于23,23在内存中的标识是这样的
0000 0000 0001 0111
15的内存表示
0000 0000 0000 1111
15取反操作的值是
1111 1111 1111 0000
所以按位与的操作就是 23 & ~15
0000 0000 0001 0111
1111 1111 1111 0000
0000 0000 0001 0000(结果)十进制的16
如果是内存分配大于16 的,比如 20 加上15表示成 35,内存表示(0000 0000 0010 0011)
按位与操作是 35 & ~15
0000 0000 0010 0011
1111 1111 1111 0000
0000 0000 0010 0000(结果) 十进制的32,
所以这就是16字节对齐的核心机制,相信我已经介绍的够清晰了,这就是iOS内存分配的最新机制。
-
步骤2 :分配内存
根据计算的内存size_t size 进行内存的分配,
-
步骤3 关联对象类型
通过InitInstanceIsa 方法进行关联类对象实例,
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}
这就是 alloc 的完整流程;
通过ISA的关联,我们就完成了一个对象的内存分配情况,也就是一个对象的alloc 的完整流程,因为任何一个类对象都可以通过ISA指针找到相应的父类,以及元类,这样对象的各种初始化实例方法都能通过ISA指针指向找到相应的IMP。从而调用方法进行消息转发。
三:总结
以上内容是个人经过老师指导,视频的回看,加上个人的整理,有什么不足的地方,还请各位大神多多指教,我一定会努力修正自己的错误。这就是我个人对alloc的流程分析。