在 Command Line Tool 下新建工程,也就是 macOS 环境下,[NSObject alloc] 不走 alloc 方法。通过可编译源码下断点也不会断到 alloc 方法里面。经过调试和查看过程,发现 [NSObject alloc] 走的方法是:
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
看到 objc_alloc 方法内部调用的还是 alloc 内部实现流程。有些迷茫的是为啥同是 alloc 方法,调用过程是不一样的呢。经学习以后发现,系统中有一个叫 LLVM(编译时就启动好了) 的东西,对它认知不多(用 c++ 编写的)。谷歌了一下:
LLVM(Low Level Virtual Machine)是一个自由软件项目,它是一种编译器基础设施,以C++写成。它是为了任意一种编程语言而写成的程序,利用虚拟技术创造出编译时期、链接时期、运行时期以及"闲置时期"的最优化。它最早以C/C++为实现对象,而目前它已支持包括ActionScript、Ada、D语言、Fortran、GLSL、Haskell、Java字节码、Objective-C、Swift、Python、Ruby、Rust、Scala以及C#等语言。
LLVM(Low Level Virtual Machine)翻译过来就是底层虚拟机 ,可以理解成一个编译器框架。也是一个项目包含了我们熟知的工具,比如:Clang,LLDB 等子项目。
查看 LLVM 源码,我用的是Visual Studio Code。借助工具查找, 在 LLVM 中找到了 objc_alloc 方法的相关内容:
/// When this method returns true, Clang will turn non-super message sends of
/// certain selectors into calls to the corresponding entrypoint:
/// alloc => objc_alloc
/// allocWithZone:nil => objc_allocWithZone
bool shouldUseRuntimeFunctionsForAlloc() const {
switch (getKind()) {
case FragileMacOSX:
return false;
case MacOSX:
return getVersion() >= VersionTuple(10, 10);
case iOS:
return getVersion() >= VersionTuple(8);
case WatchOS:
return true;
case GCC:
return false;
case GNUstep:
return false;
case ObjFW:
return false;
}
llvm_unreachable("bad kind");
}
看到这个注释 alloc => objc_alloc ,感到有些希望,顺着这个思路往下继续看看,查看这个方法在哪里调用的。
/// 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) == "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;
// 摘录一部分代码。。。。
}
通过方法名字 tryGenerateSpecializedMessageSend 也能知道主要负责消息发送的方法。从方法内部得知如果 sel 等于 alloc 进行了特殊消息处理??纯?CGF.EmitObjCAllocWithZone 方法内部做了什么。
/// Allocate the given objc object.
/// call i8* \@objc_alloc(i8* %value)
llvm::Value *CodeGenFunction::EmitObjCAlloc(llvm::Value *value,
llvm::Type *resultType) {
return emitObjCValueOperation(*this, value, resultType,
CGM.getObjCEntrypoints().objc_alloc,
"objc_alloc");
}
看到这里就能知道,上面代码注释中 alloc => objc_alloc 是为什么了。如果是 alloc 方法去调用 objc_alloc 方法 。
再看一下 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);
}
runtime 进行消息发送就会调用这个方法,首先会判断是不是特殊消息发送。如果是就会执行这段代码:
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)
/// Generate an Objective-C message send operation.
///
/// \param Method - The method being called, this may be null if synthesizing
/// a property setter or getter.
virtual CodeGen::RValue
GenerateMessageSend(CodeGen::CodeGenFunction &CGF,
ReturnValueSlot ReturnSlot,
QualType ResultType,
Selector Sel,
llvm::Value *Receiver,
const CallArgList &CallArgs,
const ObjCInterfaceDecl *Class = nullptr,
const ObjCMethodDecl *Method = nullptr) = 0;
NSObject 初始化流程
总结一下 NSObject 初始化流程:
- NSObject 的初始化早在系统级别的时候就完成了。
子类初始化流程
跟过源码发现子类初始化调用 alloc 会走两次,先会是这样:
先会去进行 alloc 消息发送,之后会再来到 callAlloc 方法调用 _objc_rootAllocWithZone 方法。是因为 alloc 属于特殊消息发送,会调用 objc_alloc 方法,objc_alloc 内部还会调用 callAlloc 。所以子类 alloc 方法会调用两次。
总结一下子类初始化流程:
在 iOS环境下 用汇编查看一下初始化流程。
看到 Person 调用 alloc 方法,其实还是调用了 objc_alloc 方法。