今天看了一些关于 Objc 相关的知识,也是一些基础部分的知识。使用汇编来查看 alloc
的实现过程。
然后分享一下 init 到底干了啥。
下面就一起看看具体流程。
我的测试代码如下
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "CTStudent.h"
#import <objc/runtime.h>
int main(int argc, char * argv[]) {
CTStudent *st = [CTStudent alloc];
NSLog(@"size %lu", class_getInstanceSize([st class]));
return 0;
}
为了不加载过多的变量及方法,我直接在Main
文件里开始了。
说明:
- CTStudent 这个类里面啥都没写。
- 使用 alloc 初始化,然后获取
st
的大小。
那么接下来就具体看看使用汇编来看看具体流程。
Note
注意请链接真机就行调试,不要用电脑,因为电脑和收回系统架构不同,汇编代码也是不同的。
在此之前介绍几个接下来要用的汇编命令
- bl 或者 b ---> 意思是
跳转到指定的函数
中去 - ret ---> 意思是
方法走到这里就返回
了,不在往下面走了
具体的可以参考网上尽详的资料。
开始
- 首先我是找
alloc
这个方法的实现。所以我使用符号断点标记alloc
, 只要经过这个方法的都会停留下来。
-
运行,他会停留在
image_objc_rootAlloc
这个函数这里.
-
那么我继续使用断点符号把
_objc_rootAlloc
也就行了标记.
- 运行发现他会走到一个
class_createInstance
的函数调用中去。
Note:如果汇编没有走到 class_createInstance
这一步之前,就不用启用 class_createInstance
符号断点,因为系统中的很多函数初始化也会走这个方法,所以我就在这个函数之前打了一个断点,如果真的走到断点符号前面的这个断点,我就会使用 register read
来读取当前内存中的值。那么我为什么会在ret
设置断点了,是因为alloc
调用完了会有返回值。
如上图所示。
大家想必都熟悉一个 Runtime 的函数,
Objc_mseSend
函数。Objc_mseSend
默认会有两个值。
Objc_mseSend(id self, SEL _cmd)
* 第一个方法的调用者
* 第二个参数是方法编号
在内存寄存器中,x0
默认是方法调用者
, x1
是方法实现编号
.
那么结合上图可以看到上面的 x0 地址,使用 po
命令即可查看对应的值。
我们可以看到是CTStudent
,正是我们 alloc
的调用者.
如果确定是我们关注的对象,那么我们就需要ge
- 我把
class_createInstance
加入断点中。
接下来会来到
class_createInstance
的调用流程中。
image
经过一系列的走位,最终被 return 出去了。
image
然后继续,被 return 到了上一次。此时控制到输出了
image那么,说明此时 上的 st
已经有大小了,大小位:8 字节。那么为啥我什么都没写却是 8 了,因为每个类中都有一个 isa
, 是 8 字节大小。
摘自 Objc4-750 源码
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
可以看出 isa
是一个 uintptr_t
类型。
那么根据汇编来看 alloc
的流程为:
alloc -> _objc_rootAlloc -> class_createInstance.
当然这里面肯定省略了很多步骤,但是使用汇编来查看代码的运行步骤是很有趣的。不妨动手试试看。当然详细的过程,我会以解析源码的过程来阐述明白。
附带今天跟踪 源码时的一张流程图。
alloc 走位图.jpg
init 走位
init 代码实现部分
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
return obj;
}
其实就是返回了自己。
那么为啥要这么做了?
其实就是让你在 init 中做一些初始化配置等操作。
想想,我们平时开发都在 init 中干了啥了。
@interface Student : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSMapTable *mapTable;
- (void)save;
- (void)log;
@end
@implementation Student
- (instancetype)init
{
self = [super init];
if (self) {
self.name = @"CT_Kare";
self.mapTable = [[NSMapTable alloc] init];
}
return self;
}
- (void)save {
[self.mapTable setObject:self.name forKey:@"name"];
}
- (void)log {
NSLog(@"self.mapTable %@", self.mapTable);
}
@end
那么,其实也是做了一些初始化操作。
总结:所以在初始化一个对象时,alloc
做了初始化准备,比如开辟对象空间,init Isa
,等操作。init 其实就是返回了初始化对象本身
,然后在 init 中做了一些初始化操作配置
而已。
网友评论