最近公司没有那么忙了,闲来无事就看下的文章,看的累了,自己也想动手写的什么,写底层的东西是很单调的,想写的好,写的通俗易懂也不容易,但人总得去尝试一下。
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里面有做了什么事情?
- 内存对齐是怎样算的呢,还有是怎样寄存的呢。
这些问题,下一篇文章就会提到,敬请期待吧。