alloc&init探索

main函数的加载流程

1.在int main前面打个断点

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {    //在这一行前面打个断点
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

然后增加一个_objc_init的符号断点




记得关闭左下角show only stack frames with debug symbols and between libraries的按钮
可以看到Xcode左侧的调用堆栈视图如下图:



通过上面的调用堆栈信息,可以得出一个简略的加载流程:

alloc&init初步探索

在ViewController.m文件中调用alloc和init方法:

NSObject *p1 = [NSObject alloc];
NSObject *p2 = [p1 init];
NSObject *p3 = [p1 init];
NSLog(@"%@ - %p", p1, &p1);
NSLog(@"%@ - %p", p2, &p2);
NSLog(@"%@ - %p", p3, &p3);

可以发现打印出来是这样:



打印出来的是同一个对象,p1,p2,p3的指针地址是不同的,但指向同一块内存,说明alloc已经创建了对象,那么对象是怎么创建的呢?
这时我们需要查看alloc的源码实现。
按住command和control键,点击alloc,进入头文件NSObject.h,只能看到alloc方法的声明,未能找到alloc方法的实现,看起来无从下手。
可以通过添加符号断点来解决这个问题,选择 Symbolic Breakpoint,在Symbol那一栏添加objc_alloc



运行程序,可以看到我们需要查找的动态链接库为libobjc.A.dylib:

探索libobjc源码

下载libobjc源码,进行配置,然后新建一个命令行项目,运行如下代码:

NSObject *obj = [NSObject alloc];

下符号断点objc_alloc,首先进入的是objc_alloc:

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

然后会来到callAlloc方法,注意此时第三个参数的值为false:

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    // 判断传入的 checkNil 是否进行判空操作
    if (slowpath(checkNil && !cls)) return nil;

    // 如果当前编译环境为 OC 2.0
#if __OBJC2__
    // 当前类没有自定义的 allocWithZone
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // 既没有实现 alloc,也没有实现 allocWithZone 就会来到这里,下面直接进行内存开辟操作。
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        // 修复没有元类的类,用人话说就是没有继承于 NSObject
        // 判断当前类是否可以快速开辟内存,注意,这里永远不会被调用,因为 canAllocFast 内部
        // 返回的是false
        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];
}

由于allocWithZone为false且cls为NSObject,所以会来到最后一句return [cls alloc];,接下来是alloc方法:

+ (id)alloc {
    return _objc_rootAlloc(self);
}
// 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*/);
}

这里第二次调用callAlloc方法,但参数不一样,第二个参数 checkNil 是 false ,因为前面在第一次调用 callAlloc 的时候进行了判空,所以这里没必要再次进行判空了。第三个参数 allocWithZone 传的是 true ,其实 allocWithZone 本质上和 alloc 是没有区别的,只是在 Objective-C 远古时代,程序员需要使用诸如 allocWithZone 来优化对象的内存结构,而在当下,其实 alloc 和 allocWithZone 在底层是一样的。
首先来到if (fastpath(!cls->ISA()->hasCustomAWZ())),判断当前类是否没有自定义的 allocWithZone:

 bool hasCustomAWZ() {
    return ! bits.hasDefaultAWZ();
 }   

 bool hasDefaultAWZ() {
     return data()->flags & RW_HAS_DEFAULT_AWZ;
 }

这里的flag指的是cls结构体内部的class_rw_t的flags,而RW_HAS_DEFAULT_AWZ 则是用来标示当前的class或者是superclass是否有默认的alloc/allocWithZone:的,这个值存储在metaclass 中。
在第一次进入callAlloc方法时,flags的值为1,与上1<<16结果为0,返回的是 false,在hasCustomAWZ这里取反,变为true,然后再在if (fastpath(!cls->ISA()->hasCustomAWZ()))里取反,为false,于是不执行if当中的内容。
第二次进入callAlloc方法时,flags的值是一个很大的整数,与上1<<16结果为1,与上面恰好相反,于是执行if当中的内容。
首先是if (fastpath(cls->canAllocFast()))

bool canAllocFast() {
     assert(!isFuture());
     return bits.canAllocFast();
}

bool canAllocFast() {
     return false;
}

可以看出,canAllocFast总是返回false,于是接下来是:

id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;            

接下来是class_createInstance方法:

id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    // 对 cls 进行判空操作
    if (!cls) return nil;
    // 断言 cls 是否实现了
    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    // cls 是否有 C++ 的初始化构造器
    bool hasCxxCtor = cls->hasCxxCtor();
    // cls 是否有 C++ 的析构器
    bool hasCxxDtor = cls->hasCxxDtor();
    // cls 是否可以分配 Nonpointer,如果是,即代表开启了内存优化 
    bool fast = cls->canAllocNonpointer();

    // 这里传入的 extraBytes 为0,然后获取 cls 的实例内存大小
    size_t size = cls->instanceSize(extraBytes);
    // 这里 outAllocatedSize 是默认值 nil,跳过
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    // 这里 zone 传入的也是nil,而 fast 拿到的是 true,所以会进入这里的逻辑
    if (!zone  &&  fast) {
        // 根据 size 开辟内存
        obj = (id)calloc(1, size);
        // 如果开辟失败,返回 nil
        if (!obj) return nil;
        // 将 cls 和是否有 C++ 析构器传入给 initInstanceIsa,实例化 isa
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        // 如果 zone 不为空,经过笔者测试,一般来说调用 alloc 不会来到这里,只有 allocWithZone
        // 或 copyWithZone 会来到下面的逻辑
        if (zone) {
            // 根据给定的 zone 和 size 开辟内存
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            // 根据 size 开辟内存
            obj = (id)calloc(1, size);
        }
        // 如果开辟失败,返回 nil
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        // 初始化 isa
        obj->initIsa(cls);
    }

    // 如果有 C++ 初始化构造器和析构器,进行优化加速整个流程
    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    // 返回最终的结果
    return obj;
}

这就是alloc的全部流程了,下面是简略的流程图:


init简略分析

init 内部实现十分简单,先来到的是 _objc_rootInit ,然后就直接返回 obj 了。其实这里是一种抽象工厂设计模式的体现,对于 NSObject 自带的 init 方法来说,其实啥也没干,但是如果你继承于 NSObject 的话,就可以去重写 initWithXXX 之类的初始化方法来做一些初始化操作。

- (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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271