最近公司没有那么忙了,闲来无事就看下掘金的文章,看的累了,自己也想动手写的什么,写底层的东西是很枯燥的,想写的好,写的通俗易懂也不容易,但人总得去尝试一下。
1.alloc汇编查看
1.1.alloc直接查看
我们直接点击alloc,进去查看一下源码。
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
到了这里能知道的就是NSObject这个类,里面有alloc这个方法。然后就什么看不到了。相当于.h文件看到,.m的实现看不到。
那我们只能改其它方式了。
1.2.alloc汇编查看
点击Debug -> Debug Workflow -> Always Show Disassembly。打开后断点就会进入汇编模式。
先 alloc一个对象。
Person *person = [Person alloc];
在 alloc那里打断点,运行代码
-> 0x107bd5e83 <+51>: movq 0x762e(%rip), %rax ; (void *)0x0000000107bdd508: Person
0x107bd5e8a <+58>: movq %rax, %rdi
0x107bd5e8d <+61>: callq 0x107bd63f6 ; symbol stub for: objc_alloc
我们捉重点,可以看到objc_alloc,好,那我们也把这个加入到断点里面。
再运行,我们可以看到代码
libobjc.A.dylib`objc_alloc:
-> 0x7fff20190b4d <+0>: testq %rdi, %rdi
0x7fff20190b50 <+3>: je 0x7fff20190b6c ; <+31>
0x7fff20190b52 <+5>: movq (%rdi), %rax
0x7fff20190b55 <+8>: testb $0x40, 0x1d(%rax)
0x7fff20190b59 <+12>: jne 0x7fff20188a75 ; _objc_rootAllocWithZone
0x7fff20190b5f <+18>: movq 0x66bbf952(%rip), %rsi ; "alloc"
0x7fff20190b66 <+25>: jmpq *0x5fe8d124(%rip) ; (void *)0x00007fff20173780: objc_msgSend
0x7fff20190b6c <+31>: xorl %eax, %eax
0x7fff20190b6e <+33>: retq
我们能看到的_objc_rootAllocWithZone。进去这个方法进行查看。
libobjc.A.dylib`_objc_rootAllocWithZone:
-> 0x7fff20188a75 <+0>: pushq %rbp
0x7fff20188a8b <+22>: testw %si, %si
0x7fff20188a8e <+25>: je 0x7fff20188aac ; <+55>
0x7fff20188a90 <+27>: movl $0x1, %edi
0x7fff20188a95 <+32>: callq 0x7fff2019323a ; symbol stub for: calloc
我们可以看到调用了calloc方法,我们就知道是开辟内存,进行分配内存空间。但更具体的我们都无法通过汇编查看。
那怎么办呢,我们接着看。
2.源码查看
源码来源可以通过苹果的openSource获取。
2.1.alloc
在NSObject.mm文件中,我们终于可以看到alloc的实现了。
+ (id)alloc {
return _objc_rootAlloc(self);
}
2.2._objec_rootAlloc
再点击_objec_rootAlloc方法,进去查看。
2.3.callAlloc
这时候进入了callocAlloc方法。
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())) {
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
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
这里的重点是 id obj = class_createInstance(cls, 0);
2.3.class_createInstance
我们看代码走到了class_createInstance方法,继续走
id
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
2.4._class_createInstanceFromZone
接着进来_class_createInstanceFromZone方法。
static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{ 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;
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
到了这里就是全文的重点了,这里就return obj对象了。
那这里的obj就是 obj = (id)calloc(1, size);
很明显和汇编分析一样,进行了calloc分配内存,那分配了多少呢,应该是和size有关。
那就是 size_t size = cls->instanceSize(extraBytes);
2.5.instanceSize
我们进去看看instanceSize方法。
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;
}
初步结论是如果大小少于16,就返回16。
我们再看看alignedInstanceSize方法。
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
可以看到有内存对齐方法。
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
# define WORD_MASK 7UL
这里的WORD_MASK是等于7,我们传进来的x是8,为什么是8呢,后面会说到。
那这里是怎么算的呢,先上图
7为 0000 01111,~为取反,则为1111 1000,15为0000 1111。
1111 1000&0000 1111为0000 1000,0000 1000就是8。
可以得到的结论是内存对齐为8个字节。
结合上面的if (size < 16) size = 16;所以size为16个字节。
那为什么要这么做呢,就是为了空间换取时间,增加效率。
2.6.initInstanceIsa
我们继续接着看
obj->initInstanceIsa(cls, hasCxxDtor);
obj分配内存后,还不知道对应的是哪个类,所以调用initInstanceIsa方法。
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
这里我们再看initIsa方法。
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}
这里的重点是调用了 isa.cls = cls;
意思就是把obj和我们的Person类关联在一起,具体指向后面我会专门写一篇文章来说明。
我们都知道,我们NSObject的类,就存放这isa指针。
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
isa指针占用8个字节,这就为什么上面内存对齐传的的x是8了,因为我们Person是没有其它属性的,只有isa。
那到这里,我们就大概清楚这个流程了。
3.init和new
3.1.init
平时我们总是alloc init,那我们alloc就能返回对象了,那init有什么用呢
+ (id)init {
return (id)self;
}
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
return obj;
}
想到了吗,还是返回自己,那这么做岂不是多此一举,你觉得呢
那肯定不是啦,这是苹果为了我们开发者更好的扩展内容用的,想想我们平时初始化
- (instancetype)init
{
self = [super init];
if (self) {
// 做的什么呢
}
return self;
}
这下子我们就懂了吧。
3.2.new
平时我们不用alloc init,会用new来替换,那new又做了些什么呢
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
好家伙,直接调用callAlloc,然后init方法,是不是就相当于alloc init了呢。
4.总结
上面看那么多的文字和代码也觉得累了,所以我们用一张图来总结一下alloc流程。
那到这里提出几个问题。
- 那这里的isa又存放着什么呢?
- calloc里面有做了什么事情?
- 内存对齐是怎么算的呢,还有是怎么存放的呢。
这些问题,下一篇文章就会说到,敬请期待吧。