写在前面
iOS中内存空间创建,对象的创建会使用到alloc;今天我们来探索一下alloc的底层步骤。
源码
Cooci司机objc4-756.2调试方案(Xcode11暂时无法断点进源码)
一.准备工作
下载好源码,经过一轮轮运行Carsh调试之后,可以通过 common+control+单击 alloc 看到底层源码的调用;
对于查看调用alloc具体源码,我们可以使用断点来分析:
· 符号断点
· 调试栏:step into
·显示汇编代码:菜单栏Debug->Debug Workflow->Always Show Disassembly
上面三种可以断到 objc_alloc 方法中
二.实际操作
//
// main.m
// objc-debug
//
// Created by mark on 2020/03/9.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSObject *object = [NSObject alloc];
NSLog(@"====== %@",object);
}
return 0;
}
不出意外大家都可以来到这边
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
然后下面到了源码部分,看到是不是开始抓头了,按住续命穴我们继续
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
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())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
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
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
源码比较多的修饰符和转义字符,看起来是会比较枯燥,不然头发为啥越来越少了呢(头发旺盛的略过),下面我们来分析一下
三.alloc 流程图
1.alloc,objc_alloc 区分
从函数栈调用分析,走的是alloc方法。
xcode10 -> alloc,xcode11 -> objc_alloc ;(使用MachOView查看两种编译下的Mach-O文件,在_Data段__la_symbol_ptr 节中,我们可以看出在Xcode11下alloc的符号会被设置为objc_alloc,而xcode10却没有)
2.callAlloc方法
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
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())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
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
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
1> slowpath(checkNil && !cls)
两个优化比较:slowpath(x),fastpath(x); slowpath(x) :x为0,希望编译器优化;x大概率是有值的,不用每次都读取。fastpath(x):表示x很可能不为0,希望编译器进行优化;
2> fastpath(!cls->ISA()->hasCustomAWZ())
hasCustomAWZ()方法表示 hasCustomAllocWithZone,这里表示没有alloc/allocWithZone的实现
3> fastpath(cls->canAllocFast())
里面调用了bit.canAllocFast 默认返回false
4> id obj = class_createInstance(cls, 0)
内部调用 _class_createInstanceFromZone(cls, extraBytes, nil)
3._class_createInstanceFromZone方法
static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
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;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
1>hasCxxCtor()
addSubclass() propagates this flag from the superclass. 判断当前class或者superclass是否有.cxx_construct
构造方法的实现
2>hasCxxDtor()
hasCxxDtor()是判断判断当前class或者superclass是否有.cxx_destruct
析构方法的实现
3>canAllocNonpointer()
anAllocNonpointer()是具体标记某个类是否支持优化的isa
4>cls->instanceSize(extraBytes)
instanceSize 获取类的大小(传入额外字节的大小)传入值为zone= false,fast = true,则(!zone && fast) = true
5>calloc()
用于动态开辟内存,没有具体实践代码。在接下来的文章里面会讲到malloc源码
6>initInstanceIsa()
内部调用initIsa(cls, true, hasCxxDtor)
初始化isa
这一步已经完成了初始化isa并开辟内存空间,那我们来看看
instanceSize
做了什么
4.字节对齐 - ( instanceSize探索)
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
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;
}
我们来过下instanceSize调用顺序
instanceSize(extraBytes) -> alignedInstanceSize ->word_align(unalignedInstanceSize())
1>instanceSize(extraBytes)
这个方法是获取类大小
2>alignedInstanceSize()
获取类所需要的内存空间大小
3>unalignedInstanceSize()
data()->ro->instanceSize
就是获取这个类所有属性内存的大小。这里只有继承NSObject
的一个属性isa
——返回8字节
4>word_align
字节对齐,在64位系统下,对象大小采用8字节对齐法
5>if (size < 16) size = 16
CoreFoundation需要所有对象之和至少是16字节
5.字节对齐算法-(实现)
假如: x = 9,已知WORD_MASK = 7
x + WORD_MASK = 9 + 7 = 16
WORD_MASK 二进制 :0000 0111 = 7 (4+2+1)
~WORD_MASK : 1111 1000
16二进制为 : 0001 0000
1111 1000
0001 0000
0001 0000 = 16
所以 x = 16 也就是 8的倍数对齐,即 8 字节对齐
总结:对象大小为16字节,必定是8的倍数
疑问:为什么要使用8字节对齐算法呢?
简单画了个示意图,上边是紧紧挨着,下面是8字节为一格。如果cpu存数据的时候紧紧挨着,读取的时候要不断变化读取长度,所以这时候就采用了空间换时间
的做法
那为什么是8字节?不是4字节或是16字节?
——因为内存中8字节的指针比较多
四.alloc 实际流程图
instanceSize
计算内存大小——量房子
calloc
申请开辟内存——造房子
initInstanceIsa
指针关联对象——房子写下名字
五.init & new
init什么也不做,就是给开发者使用工厂设计模式提供一个接口
// Replaced by CF (throws an NSException)
+ (id)init {
return (id)self;
}
- (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;
}
new 相当于调用了alloc init
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
衍生:if(self = [super init])
在 返回是instanceType 初始化时,常用到这种写法,为什么这么写呢?- 子类继承于父类属性,再判断是否为空,为空则返回nil。确保是子类调用的方法和父类对应
六 写在后面
工欲善其事必先利其器。只有在理解底层源码的同事,才有创新。从枯燥的源码慢慢啃下来,通过大神文章和gitHub大神注释理解,拆开一步步研究
实践出真知!
感谢以下大神的文章:
文章参考:iOS alloc流程