欢迎阅读iOS探索系列(按序阅读食用效果更加)
- iOS探索 alloc流程
- iOS探索 内存对齐&malloc源码
- iOS探索 isa初始化&指向分析
- iOS探索 类的结构分析
- iOS探索 cache_t分析
- iOS探索 方法的本质和方法查找流程
- iOS探索 动态方法解析和消息转发机制
- iOS探索 浅尝辄止dyld加载流程
- iOS探索 类的加载过程
- iOS探索 分类、类拓展的加载过程
- iOS探索 isa面试题分析
- iOS探索 runtime面试题分析
- iOS探索 KVC原理及自定义
- iOS探索 KVO原理及自定义
- iOS探索 多线程原理
- iOS探索 多线程之GCD应用
- iOS探索 多线程之GCD底层分析
- iOS探索 多线程之NSOperation
- iOS探索 多线程面试题分析
- iOS探索 细数iOS中的那些锁
- iOS探索 全方位解读Block
写在前面
OC作为一门万物皆对象的语言,那么对于对象创建、开辟内存的了解必不可少,本文就将探索一下alloc在底层的具体步骤
Cooci司机objc4-756.2调试方案(Xcode11暂时无法断点进源码)
一、探索方向
在源码中,我们可以通过Command+单击/右击->Jump to Defintion
的方式进入alloc
调用方法,脱离了源码我们又该如何知道它调用了什么底层方法呢?
在对象创建的代码处下个断点,等执行到断点处使用以下方法:
- Control+Step into
- 符号断点
- 菜单栏Debug->Debug Workflow->Always Show Disassembly(始终显示汇编代码)
这三种方法都能得出调用了objc_alloc
方法
二、开始探索
//
// main.m
// FXTest
//
// Created by mac on 2019/12/19.
//
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "FXPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *object1 = [NSObject alloc];
FXPerson *object2 = [FXPerson alloc];
}
return 0;
}
不出意料,各位都能来到如下方法
+ (id)alloc {
return _objc_rootAlloc(self);
}
_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];
}
本来看源码就枯燥,还有这么多if-else逻辑岔路口,就会有很多人关闭了Xcode
看啥不好看源码,是嫌自己头发太旺盛吗?
别急,我这里已经帮你掉过头发了(捋过思路了)
三、alloc源码流程
1.坑——objc_alloc、alloc傻傻分不清楚
这个坑无伤大雅,了解即可;可以简单理解为
OC对象alloc->alloc
不知道你有没有发现奇怪的一点,第二节探索方向中明明调用的是底层objc_alloc
方法,为什么在创建对象处跟进源码会来到alloc
方法呢?
函数调用栈也略有问题
这个问题还与Xcode版本有关,Xcode11->objc_alloc,Xcode10->alloc,实在令人百思不得其解
对于这个问题,目前的说法是源码开源得不够充分。以下这段代码虽然未调用到,但其逻辑也是耐人寻味。 大致猜测是这个方法等于交换单次的Method Swizzling(既然官方不开源,说明无关痛痒)
第一处:注释告诉你会调起[cls alloc]
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
另一处:如果出问题就fixMessageRef
(下断点不会被调用)
2.alloc、_objc_rootAlloc方法
前面提及过的两个方法
+ (id)alloc {
return _objc_rootAlloc(self);
}
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
3.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];
}
①if (slowpath(checkNil && !cls))判断
fastpath(x)
表示x很可能不为0,希望编译器进行优化;slowpath(x)
表示x很可能为0,希望编译器进行优化——这里表示cls大概率是有值的,编译器可以不用每次都读取 return nil 指令
②if (fastpath(!cls->ISA()->hasCustomAWZ()))判断
hasCustomAWZ
实际意义是hasCustomAllocWithZone
——这里表示有没有alloc / allocWithZone
的实现(只有不是继承NSObject/NSProxy
的类才为true)
③if (fastpath(cls->canAllocFast()))判断
内部调用了bits.canAllocFast
默认为false
④id obj = class_createInstance(cls, 0)
内部调用了_class_createInstanceFromZone(cls, extraBytes, nil)
这里有个id obj
,尝试着控制台打印一下
我们已经找到了我们想要的结果,接下来我们探索下_class_createInstanceFromZone方法是怎么将obj创建出来的
4._class_createInstanceFromZone方法
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
**********************************************************************/
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;
}
①hasCxxCtor()
hasCxxCtor()是判断当前class或者superclass是否有.cxx_construct
构造方法的实现
②hasCxxDtor()
hasCxxDtor()是判断判断当前class或者superclass是否有.cxx_destruct
析构方法的实现
③canAllocNonpointer()
anAllocNonpointer()是具体标记某个类是否支持优化的isa
④instanceSize()
instanceSize()获取类的大小(传入额外字节的大小)
已知zone=false,fast=true,则(!zone && fast)=true
⑤calloc()
用来动态开辟内存,没有具体实现代码,接下来的文章会讲到malloc源码
⑥initInstanceIsa()
内部调用initIsa(cls, true, hasCxxDtor)
初始化isa
这一步已经完成了初始化isa并开辟内存空间,那我们来看看
instanceSize
做了什么
5.字节对齐
#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;
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
// 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;
}
下面按调用顺序讲解
①size_t instanceSize(size_t extraBytes)
前面讲过——获取类的大小
②alignedInstanceSize()
获取类所需要的内存大小
③unalignedInstanceSize()
data()->ro->instanceSize
就是获取这个类所有属性内存的大小。这里只有继承NSObject
的一个属性isa
——返回8字节
④word_align
顾名思义,字节对齐——64位系统下,对象大小采用8字节对齐
⑤if (size < 16) size = 16
CoreFoundation需要所有对象之和至少是16字节
6.字节对齐算法
假如: 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字节的指针比较多
7.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;
}
补充:关于子类中if (self = [super init])
为什么要这么写——子类先继承父类的属性,再判断是否为空,如若为空没必要进行一系列操作了直接返回nil
new等于先调用alloc,再init
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
写在后面
研究源码必然是枯燥的,但是面对源码不用害怕,一步步把它拆分开来研究,多利用官方给的注释/Github大神的注释,慢慢也就啃下来了。
看别人学习津津有味,不如自己上手实际玩一下会更有收获;只看不练永远学不会,或许你学的正是别人错误的理论呢