OC做为一门万物皆对象的语言,那么对于对象建立、开辟内存的了解必不可少,本文就将探索一下alloc在底层的具体步骤c++
官方源码算法
Cooci司机objc4-756.2调试方案(Xcode11暂时没法断点进源码)设计模式
在源码中,咱们能够经过Command+单击/右击->Jump to Defintion
的方式进入alloc
调用方法,脱离了源码咱们又该如何知道它调用了什么底层方法呢?sass
在对象建立的代码处下个断点,等执行到断点处使用如下方法:bash
这三种方法都能得出调用了objc_alloc
方法app
//
// 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*/);
}
复制代码
可是接下来的源码就会让你头晕目眩,不想看了post
// 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学习
看啥很差看源码,是嫌本身头发太旺盛吗?优化
别急,我这里已经帮你掉过头发了(捋过思路了)
这个坑无伤大雅,了解便可;能够简单理解为
OC对象alloc->alloc
不知道你有没有发现奇怪的一点,第二节探索方向中明明调用的是底层objc_alloc
方法,为何在建立对象处跟进源码会来到alloc
方法呢?
函数调用栈也略有问题
这个问题还与Xcode版本有关,Xcode11->objc_alloc,Xcode10->alloc,实在使人百思不得其解
对于这个问题,目前的说法是源码开源得不够充分。如下这段代码虽然未调用到,但其逻辑也是回味无穷
大体猜想是这个方法等于交换单次的Method Swizzling(既然官方不开源,说明无关痛痒)
复制代码
前面说起过的两个方法
+ (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 (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建立出来的
/*********************************************************************** * 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
作了什么
#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字节
假如: 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 字节对齐
复制代码
这里有个疑问:为何要使用8字节对齐算法呢?
简单画了个示意图,上边是牢牢挨着,下面是8字节为一格。若是cpu存数据的时候牢牢挨着,读取的时候要不断变化读取长度,因此这时间就采用了空间换时间
的作法
那为何是8字节?不是4字节或是16字节?
——由于内存中8字节的指针比较多
instanceSize
计算内存大小——量房子
calloc
申请开辟内存——造房子
initInstanceIsa
指针关联对象——房子写下名字
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大神的注释,慢慢也就啃下来了。
看别人学习津津有味,不如本身上手实际玩一下会更有收获;只看不练永远学不会,或许你学的正是别人错误的理论呢