一、alloc的作用
作为oc语言开发者,大家都知道如何创建一个实例对象,没错就是alloc。那么alloc在底层是如何调用的呢?我们一起来探究一下。 代码搞起
LGPersion *p = [LGPersion alloc];
LGPersion *p1 = [p init];
LGPersion *p2 = [p init];
NSLog(@"p : %@---%p----%p",p,p,&p);
NSLog(@"p1: %@---%p----%p",p1,p1,&p1);
NSLog(@"p2: %@---%p----%p",p2,p2,&p2);
打印结果
p : <LGPersion: 0x6000006c0160>---0x6000006c0160----0x7ffee9ae3c58
p1: <LGPersion: 0x6000006c0160>---0x6000006c0160----0x7ffee9ae3c50
p2: <LGPersion: 0x6000006c0160>---0x6000006c0160----0x7ffee9ae3c48
通过上述代码和打印结果可以明显看出:p,p1,p2三者的对象地址是一样的,但是指针地址不一样,所以p,p1,p2三个对象指向的是同一个内存空间,在内存中的表现如图所示:
结论:alloc开辟了对象的内存空间,init并没有对空间有操作
二、探究alloc的方法
然而alloc是如何开辟内存的呢?我们该如何去探究呢?接下来我们用3种方法去探索:
1、setp into + 符号断点
先在alloc之前加个断点,然后按住control
点击setp into
进入
然后加入符号断点
objc_alloc
放开断点进入
libobjc.A.dylib objc_alloc:
2、汇编
汇编模式:debug
-> debug Workflow
->Always Show Disassembly
具体使用:进入断点后按照上述步骤即可进入汇编代码。如图:
1、断点
2、进入汇编
汇编:
3、step into
按住
control
点击step into
进入alloc
3、直接符号断点
我们都知道这个方法是alloc
所以直接下符号断点,流程如下:
先在alloc
前打断点断点之后如图
加入符号之后直接放开当前断点
然后如图所示
三、alloc源码跟踪
打开Source Browser 搜索objc
,如图:
下载objc4-818.2.tar.gz
解压
command + 左键alloc
直接跳转到
继续 command
继续,
进入
_objc_rootAllocWithZone
继续进入
_class_createInstanceFromZone
(核心代码)
如图所示(已经标出主要部分)
cls->instanceSize(extraBytes)
计算大小
calloc(1, size)
开辟空间
initInstanceIsa(cls, hasCxxDtor)
将开辟的空间跟类关联起来
细细一品确实是核心东西 ,那么下面开始挨个探究。
1、计算大小
我们首先进入instanceSize
如图
有个判断先,这里判是否有缓存 有的话直接获取返回(节省时间)
进入cache.fastInstanceSize
command 查看FAST_CACHE_ALLOC_MASK
值为0x1ff8
如图点断所示,我们需要进入align16
梳理一下这个算式
设x = 9
则算式为 (9 + 15) & ~ 15
二进制计算
x = 0000 1001
15 : 0000 1111
9 + 15 : 0001 1000
~15 :1111 0000
(9 + 15) & ~ 15 : 0001 0000 结果为16
设x = 17
则算式为 (17 + 15) & ~ 15
二进制计算
x = 0001 0001
15 : 0000 1111
17 + 15 : 0010 0000
~15 :1111 0000
(17 + 15) & ~ 15 : 0010 0000 32
所以 align16
是一个对齐方法,即返回16
的整数倍,如果不足16
,返回16
,如果x>16*n && x<16 * (n + 1)
则返回 16*(n +1)
,等同于>> 4 <<4
,该方法叫做字节对齐(简单论证不多举例了),字节对齐的意义是什么?
提高cpu读取速度:以固定的长度读取数据,会大幅度提高cup工作效率
接下来查看 普通方法 command 进入alignedInstanceSize
继续进入
unalignedInstanceSize
(没有对齐的InstanceSize
)
所以此方法返回的是对象的大小(未对齐之前的),那么对象的大小如何判断的呢?我们细想一下,对象里面有什么?属性、方法、和协议等;但是对象的大小只取决于成员变量。该方法返回的是data
中的ro
的 instanceSize
,即实例变量的大小。该方法的注释中也提到 May be unaligned depending on class's ivars.
成员变量的大小。
然后 word_align
WORD_MASK
为7
所以该方法是8字节对齐方法。
最后返回到 instanceSize
中返回
2、开辟空间
此图断点为刚声明,虽然有地址,却是脏地址,不可用的。
越过开辟的方法,之后发现地址变了,但是po一下obj
发现只是一个地址,跟平时的对象不太一样
因为接下来的步骤还没走,
class
绑定
3、绑定
进入 initInstanceIsa
进入 initIsa
完事了,我们回到 _class_createInstanceFromZone
并po,发现obj正常了,跟平时的一样,说明我的alloc走完了。
4、总结
通过以上的探索在下绘制了,简单的alloc流程图:
5、修改
在callAlloc
中,我们发现有3个return
,我们都加上断点:
然后关闭断点,运行程序进入到
alloc
前开启断点:
直接走到了
callAlloc
,没有进入到alloc
中?这是什么情况?我们再细看return
的东西objc_msgSend
消息发送,参数是cls
和alloc
,此刻发送的是alloc
方法,到底发生了什么事情?经过大神提醒和llvm
查找,发现在调用alloc
方法时候llvm
进行了方法交换,代码太长截图不方便直接上代码:
/// The ObjC runtime may provide entrypoints that are likely to be faster
/// than an ordinary message send of the appropriate selector.
///
/// The entrypoints are guaranteed to be equivalent to just sending the
/// corresponding message. If the entrypoint is implemented naively as just a
/// message send, using it is a trade-off: it sacrifices a few cycles of
/// overhead to save a small amount of code. However, it's possible for
/// runtimes to detect and special-case classes that use "standard"
/// behavior; if that's dynamically a large proportion of all objects, using
/// the entrypoint will also be faster than using a message send.
///
/// If the runtime does support a required entrypoint, then this method will
/// generate a call and return the resulting value. Otherwise it will return
/// None and the caller can generate a msgSend instead.
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;
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) )
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;
}
再细点:
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) )
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;
继续精简:
if (Sel.isUnarySelector() && Sel.getNameForSlot(0) )
return CGF.EmitObjCAlloc(Receiver, CGF.ConvertType(ResultType));
在这里将alloc
返回成了calloc
,所以才会直接进入到calloc
方法中,经过一系列判断再回到alloc
中,所以修正后的alloc
如图所示: