本文章使用环境:
Xcode: 13.3
源码objc4-838.1
测试设备: iPnone7 15.3.1
alloc
, init
方法,作为开发者相信都已经敲过无数遍了。
如果问:alloc
, init
分别有什么作用,相信大家都能回答出来分配内存和初始化。
如果再问:alloc
, init
具体是怎样实现的呢?这时候就需要通过苹果底层源码来看了。
init
我们先看一个例子:
@interface TestClass : NSObject
@property (nonatomic, copy) NSString *name;
@end
TestClass *test = [TestClass alloc];
TestClass *a = test;
TestClass *b = test;
test.name = @"test_name";
NSLog(@"name test: %@, a: %@, b: %@", test.name, a.name, b.name);
NSLog(@"地址 test: %p, a: %p, b: %p", test, a, b);
大家可能会说,代码有问题,实例创建没有进行init
,但是这个代码是可以正常运行的,输出如下:
根据输出信息,我们可以看出,alloc
之后就有分配一块完整可用的内存了,实例已经可以正常使用了。咦?那init
做了什么,这不是不需要init
了吗。
对此,我的理解是,init
是苹果给提供的一个工厂方法,系统原来的init
什么也没做。现在到源码中来验证一下。因为OC中所有类都继承自NSObject
, 所以我们找到源码的NSObject.mm
文件,从中可以找到:
- (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
方法的确什么都没有做,就是一个工厂方法。
我们还可以使用汇编来证明这一点。本次需要的汇编相关知识,请查看文章底部 汇编相关 部分。 首先修改代码
TestClass *test = [TestClass alloc];
TestClass *a = [test init];
在项目中加一个 [NSObject init]
的符号断点,当运行到 TestClass *a = [test init];
, 时打开符号断点。
可见,init
方法中就只有一个返回,没有其他的操作。我们可以打印一下寄存器的值,看到正是当前测试类的实例。
alloc
把上面示例中的类拿到源码项目中
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestClass *test = [TestClass alloc];
NSLog(@"");
}
return 0;
}
通过断点来查看,方法的调用顺序是:
callAlloc
-> alloc
-> _objc_rootAlloc
-> callAlloc
-> _objc_rootAllocWithZone
-> _class_createInstanceFromZone
奇怪了,明明是调用的alloc
方法,为什么前面会多出一个callAlloc
方法。
我们离开源码项目,到示例项目中,打上断点,当运行到 alloc
方法时,显示汇编。
从注释可以看出,这是调用了 objc_alloc
, 可是我们明明调用的是 alloc
方法。从源码中可以找到答案。
在 objc-runtime-new.mm
中,可以找到如下代码:
static void
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == @selector(alloc)) {
msg->imp = (IMP)&objc_alloc;
} else if (msg->sel == @selector(allocWithZone:)) {
msg->imp = (IMP)&objc_allocWithZone;
} else if (msg->sel == @selector(retain)) {
msg->imp = (IMP)&objc_retain;
} else if (msg->sel == @selector(release)) {
msg->imp = (IMP)&objc_release;
} else if (msg->sel == @selector(autorelease)) {
msg->imp = (IMP)&objc_autorelease;
} else {
msg->imp = &objc_msgSend_fixedup;
}
}
else if (msg->imp == &objc_msgSendSuper2_fixup) {
msg->imp = &objc_msgSendSuper2_fixedup;
}
else if (msg->imp == &objc_msgSend_stret_fixup) {
msg->imp = &objc_msgSend_stret_fixedup;
}
else if (msg->imp == &objc_msgSendSuper2_stret_fixup) {
msg->imp = &objc_msgSendSuper2_stret_fixedup;
}
#if defined(__i386__) || defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fpret_fixup) {
msg->imp = &objc_msgSend_fpret_fixedup;
}
#endif
#if defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fp2ret_fixup) {
msg->imp = &objc_msgSend_fp2ret_fixedup;
}
#endif
}
从中可以看到,方法名的大概意思是修正消息,而当消息的 sel
是 alloc
时,让 imp
指向 objc_alloc
。所以当我们调用 alloc
时,实际上调用的是 objc_alloc
的方法实现。
我们在 objc_alloc
方法打上断点,果然,调用 alloc
方法,先会走到 objc_alloc
, 然后, objc_alloc
的方法中,调用了 callAlloc
。
所以上面的调用流程是不完整的,完整的调用流程应该是:
objc_alloc
-> callAlloc
-> alloc
-> _objc_rootAlloc
-> callAlloc
-> _objc_rootAllocWithZone
-> _class_createInstanceFromZone
我们到示例代码中,通过汇编和符号断点验证一下。
打一个 objc_alloc
的符号断点
有2个调用,通过逐步运行调试,发现走的是第2个方法调用
这里可以通过寄存器数据读取( register read
)来确定相关信息, 寄存器中 x0
, x1
, x2
... 按顺序存储参数,而且 objc_msgSend
前2个默认参数 id self, sel _cmd
, 所以我们可以打印出来,当前的类和方法。
再加一个 alloc
符号断点
再加一个 _objc_rootAlloc
符号断点
再加一个 _objc_rootAllocWithZone
符号断点
在 ret
处加个断点,打印
可见 _objc_rootAllocWithZone
方法创建了实例。
汇编看到的流程跟源码中的类似,但是我们没有看到 callAlloc
方法和 _class_createInstanceFromZone
方法。这是因为有进行编译器优化的原因。
编译器优化
我们到 Build Settings
中搜索 Optimization Level
, 可见有好多种优化等级
虽然我们 Debug
模式下是 None
,但是并不意味着没有进行编译器优化,这只是可选的最低等级的优化。我们可以通过修改优化等级来证明。例如,把Debug
模式下指定为与 Release
相同的 Fastest, Smallest
模式。
None
模式下汇编
Fastest, Smallest
模式下汇编
可见优化等级越高,汇编中的指令越少。少的部分就是编译器优化掉的部分。
相关方法源码
objc_alloc
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
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));
}
alloc
+ (id)alloc {
return _objc_rootAlloc(self);
}
_objc_rootAlloc
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
_objc_rootAllocWithZone
NEVER_INLINE
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);
}
_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;
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);
}
new
TestClass *test = [TestClass new];
到源码中查看
// Calls [cls new]
id
objc_opt_new(Class cls)
{
#if __OBJC2__
if (fastpath(cls && !cls->ISA()->hasCustomCore())) {
return [callAlloc(cls, false/*checkNil*/) init];
}
#endif
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new));
}
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
可见 new
方法 就等于 alloc
+ init
汇编相关
开/关汇编显示
Xcode中 Debug
-> Debug Workflow
-> Always Show Disassembly
汇编指令
b
和 bl
: 跳转指令, 可以理解为函数调用
ret
: return的意思,可以理解为函数的返回
;
和 #
: 代表注释