一、alloc init探索
简介:
iOS中alloc是为对象申请开辟内存的方发, 初始化的时候用的最多的就是如下形式。
[[xxx alloc] init];
问题:
1、想要分析alloc从哪入手,怎么分析?
2、alloc在底层都做了什么?在底层怎么实现的?
3、alloc的时候已经创建了对象,init里面什么都没有写(看过源码才知道的),那么init到底用来干什么呢?
分析:
探索alloc常用的方法
探索alloc 从 int main 函数入手
以下是三种常用的探索手法,简单介绍
1)、下断点: control + in 找到 objc_alloc
2)、下符号断点:libobjc.A.dylib`+[NSObject alloc]
断点– symbolic Breakpoint – 对应方法名
3)、汇编查看流程(注意使用真机调试)
Debug - Debug workflow - Always show Disassembly
下载源码及环境配置
需要下载并在源码中编译,进一步探索alloc。配置如下
objc4-750源码 + Xcode11 + MacOS 10.15
开始问题分析
alloc init
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"LG_Cooci: alloc 探索");
// alloc 探索
// alloc 已经创建了对象 init
// alloc 怎么创建的呢?
// alloc 实现 - 原理 - 源码实现
// 介绍三种方式
// libobjc.A.dylib
// 1: 下断点 : control + in - objc_alloc
// 2: 下符号断点 : libobjc.A.dylib`+[NSObject alloc]:
// 3: 汇编 libobjc.A.dylib`objc_alloc:
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [p1 init];
LGPerson *p3 = [p1 init];
LGNSLog(@"%@ - %p",p1,&p1);
LGNSLog(@"%@ - %p",p2,&p2);
LGNSLog(@"%@ - %p",p3,&p3);
//打印出来的结果是p1 p2 p3结果一样,地址不一样
有三个指针,有三个指针地址。但是这三个指针指向的地址是相同给的。
3个指针指向同一个空间
}
这个时候可以知道p1 p2 p3 三个指针指向的都是[LGPerson alloc] 开辟的空间
这个时候就会有有最开始的疑问,那alloc就已经创建了对象,那init又干了什么呢?,这个是就要开始alloc的分析
alloc 分析
从int main函数入手,打断点,然后 command+左键 点击进去一步一步分析(在源码中分析)
1、alloc
NSObject.mm (在底层是混编所以是.mm)
+ (id)alloc {
return _objc_rootAlloc(self);
}
2、_objc_rootAlloc
NSObject.mm 返回一个callAlloc
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
3、callAlloc(cls,falsee,true)
NSObject.mm
// 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
目前真机使用的都是objc—2,所以下边的不会走省略不是重点
}
4、class_createInstance(cls, 0) (创建实例)
其内部调用了_class_createInstanceFromZone方法,并在其中进行size计算,内存申请,以及isa初始化
id
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
5、_class_createInstanceFromZone
根据不同的条件,选用calloc或者malloc_zone_calloc进行内存申请,并且初始化isa指针,申请完成大小为size(size的大小5.1讲述)的对象obj已经,并且返回
static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
// 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;
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
5.1、cls->instanceSize(extraBytes),计算出size,
为了方便读取和提高性能进行字节对齐,然后计算出对齐之后实际所需要的大小
其中64位系统下,对象大小采用8字节对齐,但是alloc实际申请的内存最低为16字节,也就是说系统分配内存按照16字节对齐分配
8字节对齐是用空间换取时间,保证读写速度。
// 此方法,用来字节对齐
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
// 字节对齐的详细方法,WORD_MASK在64位下为7,32位下为3,用来进行字节对齐
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
// 申请地址,此处有16字节限制
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;
}
总结:
alloc
–>_objc_rootAlloc
-> callAlloc
-> class_createInstance(创建实例)
->_class_createInstanceFromZone(从专有区创建实例)
{1、_class_instanceSize(开辟空间大小,64位8字节对齐、32位4字节对齐同时大于等于16)
2、calloc
3、objc_initInstanceIsa(实例Isa)
附上以为大神总结的流程图
扩展延伸其他知识点
1、断点控制台调试部分命令
All Variables 所有的变量
register read 读寄存器
2、OC 编译到下层的时候调用的仍是OC的方法,然后下层混编调用函数处理。下层的时候是混编
3、alloc 创建对象 申请内存空间 – 指针(有了指针不再是空乏的类LGPerson)
alloc 创建了对象,会返回相应的对象地址,返回了对象的地址就有了申请内存空间的能力,返回值存在x0。
x0既是第一个参数的传递口,也是返回值的存储地方。
先开一个内存空间(开辟的内存空间大小是8字节的倍数(64位机),同时要大于16),开辟成功后关联内存空间。
isa 是个联合体 指向其地址空间。
4、alloc
- 开辟申请内存
- 伴随初始化了isa
5、init
- _objc_rootInit(obj)
- return obj
工厂设计,交给子类去自定义重写 自由
init是规范
6、new
相当于alloc init
[callAlloc() init]
7、系统分配对象内存算法
开辟内存大小(且、取反)
(x+WORO_MASK)&~ WORO_MASK
64位#define WORO_MASK 7UL 32位 3UL
64 是8的倍数 32位是4的倍数 同时满足大于等于16
if(size < 16) size = 16;
8、内存对齐
SHIFT_NANO_QUANTUM 16 K = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM
slot_bytes = k << SHIFT_NANO_QUANTUM
系统开辟内存以16位倍数。
9、汇编分析,编译器会优化部分的代码
1、 build setting
- optimization
- Fastsamallest
2、 编译器优化的作用
- 编译时间
- 链接时间
- 运行时间
- 空闲时间
LLDB调试配置objc4 malloc源码
调试需要用真的原因是
需要真机
Arm64 X0- X30
模拟器是
X86