OC alloc 底层探索
一、alloc对象的指针地址和内存
有如下代码:
//alloc后分配了内存,有了指针。
//init所指内存地址一样,init没有对指针进行操作。
HPObject *hp1 = [HPObject alloc];
HPObject *hp2 = [hp1 init];
HPObject *hp3 = [hp1 init];
NSLog(@"%@-%p",hp1,hp1);
NSLog(@"%@-%p",hp2,hp2);
NSLog(@"%@-%p",hp3,hp3);
输出:
<HPObject: 0x600000f84330>-0x600000f84330
<HPObject: 0x600000f84330>-0x600000f84330
<HPObject: 0x600000f84330>-0x600000f84330
说明alloc
后进行了内存分配有了指针,而init
后所指内存地址一致,所以init
没有对指针进行操作。
修改NSLog
内容如下:
NSLog(@"%@-%p &p:%p",hp1,hp1,&hp1);
NSLog(@"%@-%p &p:%p",hp2,hp2,&hp2);
NSLog(@"%@-%p &p:%p",hp3,hp3,&hp3);
输出:
<HPObject: 0x600000e7c2c0>-0x600000e7c2c0 &p:0x7ffeefbf40d8
<HPObject: 0x600000e7c2c0>-0x600000e7c2c0 &p:0x7ffeefbf40d0
<HPObject: 0x600000e7c2c0>-0x600000e7c2c0 &p:0x7ffeefbf40c8
这就说明hp1
、hp2
、hp3
都指向堆空间的一块区域。而3个指针本身是在栈中连续开辟的空间,从高地址->低地址。
那么alloc
是怎么开辟的内存空间呢?
二、底层探索思路
断点结合
Step into instruction
进入调用堆栈找到关键函数:
找到了最中调用的是libobjc.A.dylib
objc_alloc:`。
下断点后通过汇编查看调用流程Debug->Debug workflow->Always Show Disassembly
:通过已知符号断点确定未知符号。
alloc
下符号断点跟踪:三、alloc源码分析
通过上面的分析已经能确定alloc
在objc
框架中,正好苹果开源了这块代码,源码:objc源码地址:Source Browser
最好是自己能编译一份能跑通的源码(也可以直接github上找别人编译好的)。当然也可以根据源码下符号断点跟踪调试。由于objc4-824
目前下载不了,这里以objc4-824.2
为例进行调试。
HPObject
定义如下:
@interface HPObject : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end
3.1 alloc
直接搜索alloc
函数的定义发现在NSObject.mm 2543
,通过断点调试类。
调用alloc
会首先调用objc_alloc
:
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
callAlloc
会走到调用alloc
分支。
+ (id)alloc {
return _objc_rootAlloc(self);
}
alloc
直接调用了_objc_rootAlloc
:
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
_objc_rootAlloc
传递参数checkNil
为false
,allocWithZone
为true
直接调用了callAlloc
。- 在调用
objc_alloc
的时候传递的checkNil
为true
,allocWithZone
为false
。
这里没什么好说的只是方法的一些封装,具体实现要看callAlloc
。
3.2 callAlloc
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
//表示值为假的可能性更大。即执行else里面语句的机会更大
if (slowpath(checkNil && !cls)) return nil;
//hasCustomAWZ方法判断是否实现自定义的allocWithZone方法,如果没有实现就调用系统默认的allocWithZone方法。
//表示值为真的可能性更大;即执行if里面语句的机会更大
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
slowpath
:表示值为假的可能性更大。即执行else里面语句的机会更大。fastpath
:表示值为真的可能性更大;即执行if里面语句的机会更大。OBJC2
:是因为有两个版本。Legacy版本
(早期版本,对应Objective-C 1.0
) 和Modern版本
(现行版本Objective-C 2.0
)。
- 在首次调用的时候会走
alloc
分支进入到alloc
逻辑。 hasCustomAWZ
意思是hasCustomAllocWithZone
有没有自定义实现AllocWithZone
。没有实现就走(这里进行了取反)_objc_rootAllocWithZone
,实现了走allocWithZone:
。- 第二次调用直接走
callAlloc
的其它分支不会调用到alloc
。
⚠️:自己实现一个类的allocWithZone
alloc
分支就每次都被调用了
3.3 _objc_rootAllocWithZone
NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
_objc_rootAllocWithZone
直接调用了_class_createInstanceFromZone
。
3.4 allocWithZone
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
_objc_rootAllocWithZone
直接调用了_objc_rootAllocWithZone
,与上面的3.3
中的逻辑汇合了。
3.5 _class_createInstanceFromZone
最终会调用_class_createInstanceFromZone
进程内存的计算和分配。
static ALWAYS_INLINE id
_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());
// Read class's info bits all at once for performance
//判断当前class或者superclass是否有.cxx_construct构造方法的实现
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
//判断当前class或者superclass是否有.cxx_destruct析构方法的实现
bool hasCxxDtor = cls->hasCxxDtor();
//标记类是否支持优化的isa
bool fast = cls->canAllocNonpointer();
size_t size;
//通过内存对齐得到实例大小,extraBytes是由对象所拥有的实例变量决定的。
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;
}
//初始化实例isa指针
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
计算空间大小。根据zone
是否有值调用malloc_zone_calloc
和calloc
进行内存分配。在
calloc
之前分配的obj
是一块脏内存,执行calloc
后才会真正分配内存。执行前后内存地址发生了变化。!zone && fast
分别调用initInstanceIsa
和initIsa
进行isa
实例化。- 执行完
initInstanceIsa
后再次打印就有类型了。 - 根据是否有
hasCxxCtor
分别返回obj
和调用object_cxxConstructFromClass
。
3.6 instanceSize 申请内存
在这个函数中调用了instanceSize
计算实例大小:
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
- 没有缓存的话会调用
alignedInstanceSize
,如果最终的size < 16
会返回16
。 - 有缓存则调用
fastInstanceSize
。 - 正常情况下缓存是在
_read_images
的时候生成的。所以这里一般会走fastInstanceSize
分支。
3.6.1 alignedInstanceSize
#ifdef __LP64__
# define WORD_MASK 7UL
#else
# define WORD_MASK 3UL
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
x
为unalignedInstanceSize
获取。读取的是data()->ro()->instanceSize
实例变量的大小。由ivars
决定。这里为8
,因为默认有个isa
。isa
为Class
,Class
为objc_class
struct *
类型。@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
- 字节对齐算法为:
(x + WORD_MASK) & ~WORD_MASK
。WORD_MASK
64
位下为7
,32
位下为3
。
那么对于HPObject
对象计算方法如下:
根据公式可得1:(8 + 7) & ~7
等价于(8 + 7) >>3 << 3
。
根据1可得2:15 & ~7
转换为二进制:0000 1111 & ~0000 0111
=0000 1111 & 1111 1000
计算可得:00001000 = 8
所以alignedInstanceSize
计算就是以8
字节对齐取8
的倍数(算法中是往下取,对于内存分配来讲是往上取)。
那么为什么以8
字节对齐,最后最小分配16
呢?
分配16
是为了做容错处理。以8
字节对齐(选择8
字节是因为8
字节类型是最常用最多的)是以空间换取时间,提高CPU
读取速度。当然这过程中会做一定的优化。
3.6.2 fastInstanceSize
bool hasFastInstanceSize(size_t extra) const
{
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
}
return _flags & FAST_CACHE_ALLOC_MASK;
}
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
fastInstanceSize
中会调用align16
,实现如下(16字节对齐):
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
void setInstanceSize(uint32_t newSize) {
ASSERT(isRealized());
ASSERT(data()->flags & RW_REALIZING);
auto ro = data()->ro();
if (newSize != ro->instanceSize) {
ASSERT(data()->flags & RW_COPIED_RO);
*const_cast<uint32_t *>(&ro->instanceSize) = newSize;
}
cache.setFastInstanceSize(newSize);
}
在size
变化只会走会更新在缓存中。那么调用setInstanceSize
的地方如下:
realizeClassWithoutSwift
:类加载的时候计算。这里包括懒加载和非懒加载。这里会调用方法,根据类的实例变量进行size
计算。这里是在_read_images
的时候调用。class_addIvar
:动态添加属性的时候会重新计算实例大小。objc_initializeClassPair_internal
:动态添加类相关的初始化。
instanceSize
对于HPObject
而言分配内存大小应该为8(isa) + 8(name)+4(age)= 20
根据内存对齐应该分配24
字节。
3.7 initInstanceIsa
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
initInstanceIsa
最终会调用initIsa
。initIsa
最后会对isa
进行绑定:
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
isa_t
是一个union
。nonpointer
表示是否进行指针优化。不优化直接走setClass
逻辑,优化走else
逻辑。
链接:https://www.jianshu.com/p/884275c811d5