咱们写代码的时候老是会alloc
,以下,咱们知道它是在为对象开辟内存空间,那么具体的开辟流程是怎么样的呢,接下来咱们开始对其研究。c++
XDPerson *person = [XDPerson alloc];
复制代码
Debug
->Debug Workflow
->Always Show Disassembly
。咱们能够看到汇编代码,而且在汇编文件的顶部看到
libobjc.A.dylib objc_alloc
。git
legecy
->objc1
,modern
->objc2
。objc2
版本。llvm
源码,源码比较大,能够自行下载查看。源码里面写代码来探索,一样是这么一行代码。github
XDPerson *person = [XDPerson alloc];
复制代码
现象: 咱们断点在alloc
这行,运行起来以后能够进入到源码里面。发现直接进入到alloc
类方法,下一步就是_objc_rootAlloc
这个函数。算法
这个时候咱们有疑问,不是应该alloc
类方法以后的下一步是objc_alloc
吗?sass
没错,这个过程是编译器直接给咱们优化了,也就间接了说明了
objc4
源码并无真正的开源,只作到了部分的开源,这里分为两个阶段,编译阶段llvm
源码 + 运行阶段objc
源码。安全
llvm
探索llvm
很友好,它提供了一系列的测试代码,能够供咱们去理性理解分析,接下来开始咱们的探索之路。bash
测试代码:test_alloc_class_ptr
搜索app
// Make sure we get a bitcast on the return type as the
// call will return i8* which we have to cast to A*
// CHECK-LABEL: define {{.*}}void @test_alloc_class_ptr
A* test_alloc_class_ptr() {
// CALLS: {{call.*@objc_alloc}}
// CALLS-NEXT: bitcast i8*
// CALLS-NEXT: ret
return [B alloc];
}
// Make sure we get a bitcast on the return type as the
// call will return i8* which we have to cast to A*
// CHECK-LABEL: define {{.*}}void @test_alloc_class_ptr
A* test_allocWithZone_class_ptr() {
// CALLS: {{call.*@objc_allocWithZone}}
// CALLS-NEXT: bitcast i8*
// CALLS-NEXT: ret
return [B allocWithZone:nil];
}
复制代码
全局搜索
test_alloc_class_ptr
咱们能够看到一段测试的代码的相关说明,意思就是告诉开发者调用objc_alloc
以后会继续走alloc
的流程。函数
objc_alloc
探索测试
查找目标: objc_alloc
搜索
llvm::Value *CodeGenFunction::EmitObjCAlloc(llvm::Value *value,
llvm::Type *resultType) {
return emitObjCValueOperation(*this, value, resultType,
CGM.getObjCEntrypoints().objc_alloc,
"objc_alloc");
}
复制代码
往上追溯:EmitObjCAlloc
搜索
static Optional<llvm::Value *>
tryGenerateSpecializedMessageSend(CodeGenFunction &CGF, QualType ResultType,
llvm::Value *Receiver,
const CallArgList& Args, Selector Sel,
const ObjCMethodDecl *method,
bool isClassMessage) {
auto &CGM = CGF.CGM;
if (!CGM.getCodeGenOpts().ObjCConvertMessagesToRuntimeCalls)
return None;
// objc_alloc
// 2: 只是去读取字符串
// 3:
// 4:
auto &Runtime = CGM.getLangOpts().ObjCRuntime;
switch (Sel.getMethodFamily()) {
case OMF_alloc:
if (isClassMessage &&
Runtime.shouldUseRuntimeFunctionsForAlloc() &&
ResultType->isObjCObjectPointerType()) {
// [Foo alloc] -> objc_alloc(Foo) or
// [self alloc] -> objc_alloc(self)
if (Sel.isUnarySelector() && Sel.getNameForSlot(0) == "alloc")
return CGF.EmitObjCAlloc(Receiver, CGF.ConvertType(ResultType));
// [Foo allocWithZone:nil] -> objc_allocWithZone(Foo) or
// [self allocWithZone:nil] -> objc_allocWithZone(self)
if (Sel.isKeywordSelector() && Sel.getNumArgs() == 1 &&
Args.size() == 1 && Args.front().getType()->isPointerType() &&
Sel.getNameForSlot(0) == "allocWithZone") {
const llvm::Value* arg = Args.front().getKnownRValue().getScalarVal();
if (isa<llvm::ConstantPointerNull>(arg))
return CGF.EmitObjCAllocWithZone(Receiver,
CGF.ConvertType(ResultType));
return None;
}
}
break;
case OMF_autorelease:
if (ResultType->isObjCObjectPointerType() &&
CGM.getLangOpts().getGC() == LangOptions::NonGC &&
Runtime.shouldUseARCFunctionsForRetainRelease())
return CGF.EmitObjCAutorelease(Receiver, CGF.ConvertType(ResultType));
break;
case OMF_retain:
if (ResultType->isObjCObjectPointerType() &&
CGM.getLangOpts().getGC() == LangOptions::NonGC &&
Runtime.shouldUseARCFunctionsForRetainRelease())
return CGF.EmitObjCRetainNonBlock(Receiver, CGF.ConvertType(ResultType));
break;
case OMF_release:
if (ResultType->isVoidType() &&
CGM.getLangOpts().getGC() == LangOptions::NonGC &&
Runtime.shouldUseARCFunctionsForRetainRelease()) {
CGF.EmitObjCRelease(Receiver, ARCPreciseLifetime);
return nullptr;
}
break;
default:
break;
}
return None;
}
复制代码
看到这段代码,咱们好熟悉呀,
alloc
、autorelease
、retain
、release
,这里其实就是编译阶段符号绑定symblos
的相关信息。
继续往上追溯:tryGenerateSpecializedMessageSend
搜索
CodeGen::RValue CGObjCRuntime::GeneratePossiblySpecializedMessageSend(
CodeGenFunction &CGF, ReturnValueSlot Return, QualType ResultType,
Selector Sel, llvm::Value *Receiver, const CallArgList &Args,
const ObjCInterfaceDecl *OID, const ObjCMethodDecl *Method,
bool isClassMessage) {
if (Optional<llvm::Value *> SpecializedResult =
tryGenerateSpecializedMessageSend(CGF, ResultType, Receiver, Args,
Sel, Method, isClassMessage)) {
return RValue::get(SpecializedResult.getValue());
}
return GenerateMessageSend(CGF, Return, ResultType, Sel, Receiver, Args, OID,
Method);
}
复制代码
截止追踪源头到这里就结束了。
咱们从源码的流程中大体的能够获得这个一个过程,
alloc
特殊名称消息来了以后就会走objc_alloc
消息调用流程。
objc_alloc
在objc4
源码里面会最终调用[cls alloc]
,它是一个没有返回值的函数,虽然也是alloc
特殊函数名称,可是在咱们追踪到源头的位置if
条件里面没有成立,因而就直接走了通用消息查找流程。
笔者只对llvm
作了一下简单的流程分析,里面还有不少小细节,能够去探索发现。
objc
探索下面咱们开始对咱们感兴趣的objc
源码进行分析。有了前面的llvm
的过程,咱们就能够直接进入源码查看alloc
的流程了。在下面的分析中,咱们分为两块,alloc
主线流程 + 具体函数分支流程。
alloc
主线流程alloc
的入口
+ (id)alloc {
return _objc_rootAlloc(self);
}
复制代码
OC
方法进入到C
函数里面。_objc_rootAlloc
分析
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*a*/, true/*allocWithZone*/);
}
复制代码
callAlloc
,入参cls
类,false
->checkNil
,true
->allocWithZone
。callAlloc
分析
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast is summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
复制代码
if (slowpath(checkNil && !cls)) return nil;
条件判断,slowpath
表示条件极可能为false
。
if (fastpath(!cls->ISA()->hasCustomAWZ()))
,fastpath
表示条件极可能为true
。
if (fastpath(cls->canAllocFast()))
当前cls
是否能快速alloc
。
bool dtor = cls->hasCxxDtor();
当前的cls
是否有c++
的析构函数。
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
让系统开辟内存空间。
callBadAllocHandler(cls);
表示alloc
失败。
obj->initInstanceIsa(cls, dtor);
初始化isa
。
id obj = class_createInstance(cls, 0);
建立实例化对象,咱们会在下面具体针对这个函数作讲解。
if (allocWithZone) return [cls allocWithZone:nil];
当前类实现了allocWithZone
函数,就调用类的allocWithZone
方法。
经过callAlloc
函数的调用流程,alloc
的主线流程咱们大体了解了。
下面咱们针对具体函数作分析
cls->ISA()->hasCustomAWZ()
cls->ISA()
经过对象的isa
的值 经过一个&
运算isa.bits & ISA_MASK
找到当前类的元类。hasCustomAWZ()
判断元类里面是否有自定义的allocWithZone
,这个与咱们在类里面写的allocWithZone
是不一样。cls->canAllocFast()
false
,这段部分涉及到objc_class
结构体里面的相关解释,这里就不作展开说明了。[cls allocWithZone:nil]
_objc_rootAllocWithZone
。objc2
版本,直接进入class_createInstance(cls, o)
。class_createInstance(cls, 0)
笔者会对这个函数着重分析,咱们能够发现_objc_rootAllocWithZone
最后也是调用的这个函数。
流程分析:
id
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
复制代码
咱们能够看到实际调用是_class_createInstanceFromZone
,入参cls
类,extraBytes
值为0。
static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
// Read class is info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
}
else {
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
复制代码
size_t extraBytes 为0。 void *zone 指针为nil。
bool hasCxxCtor = cls->hasCxxCtor(); 是否有
c++
构造函数。
bool hasCxxDtor = cls->hasCxxDtor(); 是否有
c++
析构函数。
bool fast = cls->canAllocNonpointer(); 是否能建立
nonPointer
,这里的结果是true
,之后咱们会作相应的介绍。
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
//WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
复制代码
最后一步的就是内存对齐的算法
- 经过演算 ( 8 + 7 ) & (~7) 咱们转话成2进制以后计算 值为8,同时这里也能够说明对象申请内存的时候是以8字节对齐,意思就是申请的内存大小是8的倍数。
if (size < 16) size = 16;
这一步又给咱们表达了申请内存小于16字节的,按照16字节返回。这里是为了后面系统开辟内存空间大小作的一致性原则的处理。
calloc
与 malloc_zone_calloc
去根据申请的内存空间大小size
让系统开辟内存空间给obj
对象。
这里涉及到另外一份源码
libmalloc
。咱们就不展开分析了,可是咱们要知道,malloc
开辟内存空间的原则是按照16字节对齐的。
initInstanceIsa()
会调用
initIsa(cls, true, hasCxxDtor);
函数
isa_t newisa(0);
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;
isa = newisa;
复制代码
相应的代码简化以后就是这么几部流程,都是来初始化
isa
到此为止,alloc
的分析流程就结束了。这里附上一个相应的alloc
主线流程图。
从源码入手
- (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;
}
复制代码
这里就比较简单了,直接
_objc_rootInit
以后就返回了obj
,说明init
没作啥事,返回的是alloc
出来的obj
。
从源码入手
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
复制代码
new
实际上就是走了alloc
流程里面的步骤,而后在+init
。
XDPerson *p1 = [XDPerson alloc];
XDPerson *p2 = [p1 init];
XDPerson *p3 = [p1 init];
NSLog(@"p1 - %@,%p",p1, &p1);
NSLog(@"p2 - %@,%p",p2, &p2);
NSLog(@"p3 - %@,%p",p3, &p3);
复制代码
答案:
咱们能够分析
init
实际上返回的结果就是alloc
出来的对象,所以p1
、p2
、p3
指向的是同一个内存地址,可是它们的指针地址自己就是不一样的。
2019-12-29 20:09:02.971814+0800 XDTest[1809:186909] p1 - <XDPerson: 0x100f33010>,0x7ffeefbff5b8
2019-12-29 20:09:02.971978+0800 XDTest[1809:186909] p2 - <XDPerson: 0x100f33010>,0x7ffeefbff5b0
2019-12-29 20:09:02.972026+0800 XDTest[1809:186909] p3 - <XDPerson: 0x100f33010>,0x7ffeefbff5a8
复制代码
init
作了什么?这样设计的好处?解释一下if (self = [super init])
?init
实际上就是直接返回了alloc
里面建立的objc
。initWith...
这样的方法,让开发者更好的自定义。self = [super init]
响应继承链上父类的init
方法,防止父类实现了某些特殊的方法,到了本身这里被丢弃。加上if
处理,咱们能够理解为一层安全的判断,防止父类在init
里面直接返回nil
。