前言
作为一名iOS开发者,想要进阶,底层原理是必经之路,也是前往大厂的筹码之一。接下来,我将从最基础的代码开始,探索iOS底层原理。那么如何探索呢?我们今天就从alloc
开始征程。
alloc对象的指针地址和内存
XKPerson *objc1 = [XKPerson alloc];
XKPerson *objc2 = [objc1 init];
XKPerson *objc3 = [objc1 init];
NSLog(@"%@ - %p - %p",objc1,objc1,&objc1);
NSLog(@"%@ - %p - %p",objc2,objc2,&objc2);
NSLog(@"%@ - %p - %p",objc3,objc3,&objc3);
打印结果:
alloc&init初探[5783:115470] - 0x6000030406b0 - 0x16f16ff28
alloc&init初探[5783:115470] - 0x6000030406b0 - 0x16f16ff20
alloc&init初探[5783:115470] - 0x6000030406b0 - 0x16f16ff18
根据打印结果,我们可以得出以下结论:
-
obj1
、obj2
、obj3
打印的对象及其地址是一样的,但是指针地址是不一样的,这三个指针指向了同一块内存空间 -
alloc
会创建一块内存 -
init
不会对当前的指针做任何操作
总结:
-
alloc
可以开辟出一块内存空间,而init
不能开启出内存空间。 - 堆区的内存地址是从低地址到高地址, 栈区的内存地址是从高地址到低地址。这样堆区和栈区内存就不会有具体的大小分界线,有利于堆区和栈区内存大小的动态变化。
源码准备工作
- 下载 苹果官方:objc4-818.2 源码(PS: 目前824无法下载),编译可以思路可参考 源码编译调试
- 编译好的源码 gitHub:objc4-818.2(PS:如果是M1电脑请注释下图两处代码:)
- 这里给大家提供一下苹果官方源码库,大家一定不要放过它
alloc源码初探
这里以objc4-818.2
版本的源码为例进行探索,接下来请大家集中注意力,认真观看,现在开始……
废话不都说先给大家来一张alloc
底层源码流程图:
接下来我们根据这张流程图,一步一步开始解析:
第一步:在main函数里面写下NSObject *objc = [NSObject alloc];
并进入 alloc
中探索
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *objc = [NSObject alloc];
}
return 0;
}
第二步:进入_objc_rootAlloc
中探索
+ (id)alloc {
return _objc_rootAlloc(self);
}
第三步:进入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*/);
}
第四步:这里就到了核心方法:_objc_rootAllocWithZone
,这个方法就是在开辟内存空间,下面继续探索如何开辟内存空间的,继续往下看……
// 这里是核心方法
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
// 传入的checkNil为nil, !cls为nil, 所以 slowpath为false
if (slowpath(checkNil && !cls)) return nil;
// 判断类或父类是否有alloc/allocWithZone的默认实现
// fastpath为真值判断
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
和fastpath
// __builtin_expect(bool(x), 1) 表示x很有可能为真
#define fastpath(x) (__builtin_expect(bool(x), 1))
// __builtin_expect(bool(x), 0) 表示x很有可能为假
#define slowpath(x) (__builtin_expect(bool(x), 0))
- 注解二:
hasCustomAWZ()
bool hasCustomAWZ() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
// class or superclass has default alloc/allocWithZone: implementation(类或父类是否有alloc/allocWithZone的默认实现)
// Note this is is stored in the metaclass.
#define FAST_CACHE_HAS_DEFAULT_AWZ (1<<14)
}
第五步:进入_class_createInstanceFromZone
中继续探索
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);
}
第六步:这里就是重中之重了,认真看,这里有三句重要代码分别是:size = cls->instanceSize(extraBytes);
、obj = (id)calloc(1, size);
、obj->initInstanceIsa(cls, hasCxxDtor);
。
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
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
// 计算开辟内存空间的大小
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;
}
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);
}
-
size = cls->instanceSize(extraBytes);
计算需要的内存大小
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;
}
// objc2基本可以快速实例
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;
}
// 快速开辟内存空间,最少16个字节,为什么最少要16个字节且是16字节对齐呢?
// 苹果采取16字节对齐,是因为OC的对象中,至少会有一个叫isa指针,它是必然存在的,而且它就占了8字节,即使对象中没有其它的属性了,那对象就至少要占用8字节。
// 如果是8字节对齐,那么如果有两块连续的对象内存都没有任何属性,只有一个isa,那么这两个对象的内存空间就会完全的挨在一起,容易产生错乱,为了尽量避免这种问题同时不浪费内存空间,就使用了16字节对齐,这样就能保证CPU的高效读取,达到以空间换时间的效果。
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);
}
}
// 16字节对齐
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
-
obj = (id)calloc(1, size);;
根据内存空间大小向系统申请内存地址指针
通过 instanceSize方法计算出所需的内存空间大小,然后向系统申请这个大小的内存,返回给obj,因此obj是指向内存地址的指针,注意,在没有给obj分配内存地址前,它里面是保存了一个地址,不过这是一个脏地址。
obj->initInstanceIsa(cls, hasCxxDtor);
将类与内存地址进行关联
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
到这里alloc的初探就告一段落,精彩仍将继续,请持续关注。