1、怎么去探究alloc?
我们在xcode中按住command键点进去就进入了一下页面
+ (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
\+ (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
\+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
\- (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer");
\- (void)finalize OBJC_DEPRECATED("Objective-C garbage collection is no longer supported");
\- (id)copy;
\- (id)mutableCopy;
到这个页面之后,就再也无法进去了,臣妾想啊,但是要不到啊。
正确的方法是:
去苹果的开源库中去下载相应的代码,去做进一步探究。附链接
当然这个也是太麻烦了。还需要各种配置才能运行起来,像我们这么“勤快的小蜜蜂”,当然是找别人配置好的呀。比如我撸过来的,可以直接用了
2、alloc的流程
源码下载之后,我们直接最新的818运行起来,新建一个文件ELPerson。
我们断点先断上,待他停住之后,我们直接上图。
#import <Foundation/Foundation.h>
#import "ELPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
ELPerson *per = [ELPerson alloc];
NSLog(@"%@",per);
}
return 0;
}
然后,我们摁住command+Step into,这样就进去了。
libobjc.A.dylib`+[NSObject alloc]:
0x10033dd60 <+0>: pushq %rbp
0x10033dd61 <+1>: movq %rsp, %rbp
0x10033dd64 <+4>: subq $0x10, %rsp
0x10033dd68 <+8>: movq %rdi, -0x8(%rbp)
0x10033dd6c <+12>: movq %rsi, -0x10(%rbp)
-> 0x10033dd70 <+16>: movq -0x8(%rbp), %rdi
0x10033dd74 <+20>: callq 0x1002d86f0 ; _objc_rootAlloc at NSObject.mm:1948
0x10033dd79 <+25>: addq $0x10, %rsp
0x10033dd7d <+29>: popq %rbp
0x10033dd7e <+30>: retq
进去之后我们就来到这个页面,这个最上面的意思就是,方法调用到了libobjc.A.dylib这个动态库里面去了,汇编代码看不懂没关系,我们继续往下看看。后面有个
callq 0x1002d86f0 ; _objc_rootAlloc at NSObject.mm:1948
很明显调用_objc_rootAlloc方法去了。这样我们就可以看到,方法调用的流程了。但是这个方法太不友好了。里面都是汇编,操作麻烦。很容易打击我们探索的积极性。所以我们直接摁住command找到alloc点进去
+ (id)alloc {
return _objc_rootAlloc(self);
}
我们继续进去
callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
这样就可以了 ,简单易懂。遇到if()else()这样的分支,我们直接下断点去分析就好了
前面的流程基本都是无意义的方法调用,我们直接找到
_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());
// 一次读取类的信息以提高性能
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);
}
我们点击instanceSize()进去之后看看实现
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF 要求所有对象至少为16个字节。
if (size < 16) size = 16;
return size;
}
我们看看,这个地方已经加入了缓存,可能苹果觉得不加缓存太慢了,在我们新的版本中加入了缓存。下面是一个字节对齐算法。CF 要求所有对象至少为 16 个字节。
我们直接打印下,验证也可以看出来进行了字节对齐
2021-06-07 12:37:11.197898+0800 KCObjcBuild[53421:1865440] <ELPerson: 0x1018100a0>**
**(lldb)** **po extraBytes**
88
**(lldb)** **po size**
96
再看下
size_t size = alignedInstanceSize() + extraBytes;
里面的实现是
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
uint32_t unalignedInstanceSize() const
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
//OC对象成员变量8字节对齐运算
static inline uint32_t word_align(uint32_t x) {
eturn (x + WORD_MASK) & ~WORD_MASK;
}
再继续探索,就来到开辟内存
obj = (id)calloc(1, size);
最后调用object_cxxConstructFromClass(),从基类开始递归调用,构造成功就返回我们的对象。
id
object_cxxConstructFromClass(id obj, Class cls, int flags)
{
ASSERT(cls->hasCxxCtor()); // required for performance, not correctness
id (*ctor)(id);
Class supercls;
supercls = cls->getSuperclass();
// Call superclasses' ctors first, if any.
if (supercls && supercls->hasCxxCtor()) {
bool ok = object_cxxConstructFromClass(obj, supercls, flags);
if (slowpath(!ok)) return nil; // some superclass's ctor failed - give up
}
// Find this class's ctor, if any.
ctor = (id(*)(id))lookupMethodInClassAndLoadCache(cls, SEL_cxx_construct);
if (ctor == (id(*)(id))_objc_msgForward_impcache) return obj; // no ctor - ok
// Call this class's ctor.
if (PrintCxxCtors) {
_objc_inform("CXX: calling C++ constructors for class %s",
cls->nameForLogging());
}
if (fastpath((*ctor)(obj))) return obj; // ctor called and succeeded - ok
supercls = cls->getSuperclass(); // this reload avoids a spill on the stack
// This class's ctor was called and failed.
// Call superclasses's dtors to clean up.
if (supercls) object_cxxDestructFromClass(obj, supercls);
if (flags & OBJECT_CONSTRUCT_FREE_ONFAILURE) free(obj);
if (flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
3、去验证内存
我们先给ELPerson添加几个属性,赋值之后断点断上,然后
(lldb)** **x per**
0x100c2c870: 75 82 00 00 01 80 1d 01 30 40 00 00 01 00 00 00 u.......0@......
0x100c2c880: 00 00 00 00 00 00 00 00 12 00 00 00 00 00 00 00 ................
也可以View Memory查看内存分布
至此,我们的探索流程就告一段落了。