iOS底层探索的流程 & alloc以及init初探

序言

  探索底层,我们开始从平时最经常用的创建对象的alloc & init开始,为了能纯粹的单独研究alloc或者init到底在底层究竟干了什么,我们把他们两个单独拆开,并打印对象本身,分配的内存地址和栈内存看一下,如下图:

image.png

结论:
1、自定义类调用alloc类方法,分配的堆内存只有一个,init方法并没有分配内存
2、alloc分配的堆内存会与PSYPerson进行绑定
3、person、pers1、pers2栈内存同指向同一个堆内存

分析底层的三种方式

1、符号断点

按照图片标号顺序添加符号断点 在Symbol栏输入要下断点的符号

通过下符号断点跟进流程

2、源码跟进流程

  下载要分析的底层源码 源码下载地址 如要下载objc源码,可依次选择:macOS --> 10.15.6 --> command + F 搜索objc --> objc4-787.1 --> 点击后面的下载按钮

得到源码可以做一些配置,可参考这里 mac 10.15下的objc4-779.1源码编译objc4-756.2源码编译,使其可以编译运行,然后借助control + step in,一步一步跟进。

3、汇编跟进流程

  依次点击Debug --> Debug W0rkflow --> Always Show Disassembly,下断点运行之后就可以看到其汇编实现过程。还可以通过下内存断点,一直跟进去。
lldb断点语法基本使用方法:
b + 内存地址 下断点
breakpoint list 列出所有断点列表
breakpoint delete +断点标号 可删除某一条/组断点
breakpoint delete 删除所有断点
breakpoint enable 启用所有断点
breakpoint enable + 断电标号 启用指定的断点
breakpoint disable + 断电标号 禁用指定的断点

image.png

小结:个人认为,三种探索底层的方式并不是独立的,应该是相互结合使用,进一步的验证、完善和总结,才能得到相对完善的论证结果。

alloc原理初探

1、符号断点方式探索alloc流程

  首先禁用掉所有的断点,让程序跑到到 [PSYPerson alloc] 调用之前(因为应用在启动的时候,存在超多的 alloc 方法,包括 dyld 动态加载过程,禁用掉断点就是要过滤掉其他的 alloc ,单纯分析 PSYPerson调用的 alloc ),重新启用alloc这一个断点,然后继续执行,可以看到, [PSYPerson alloc]方法会调用[NSobject alloc]方法,因为PSYPerson类本身并没有alloc类方法,所以会跑到其继承的父类中查找alloc

image.png

  如下图,可以知道在 NSObjectalloc的实现里面,调用_objc_rootAlloc方法。添加符号断点_objc_rootAlloc,继续执行,就跑到了_objc_rootAlloc:方法,里面会有两个分支,分别是:class_createInstanceobjc_msgSend,然后再下两个符号断点class_createInstanceobjc_msgSend,继续执行看会走哪一个分支。以此类推,即可得到比较清晰的流程。

_objc_rootAlloc:的调用

2、源码分析方式探索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*/);
}

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
// 核心方法
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    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));
}


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);
}


/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
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);
}

3、汇编跟进方式探索alloc流程

  利用底层探索方式三汇编跟进流程,Xcode调起汇编方式:Debug --> Debug W0rkflow --> Always Show Disassembly,结合符号断点,或者内存断点进行跟进流程。
通过下符号断点:alloc,lldb执行c即可断到libobjc.A.dylib +[NSObject alloc]: ---> b 0x1ac981638 _objc_rootAlloc,然后我们主要关注关于bbl指令,对后面的内存地址进行下内存地址,如下:

image.png
image.png
image.png

libobjc.A.dylib _objc_msgSend_uncached

image.png

得到libobjc.A.dylib _class_lookupMethodAndLoadCache3,此时发现走的流程比较深了,跑到了Runtime消息转发的流程,查找IMP,所以及时刹车。
结合上面三种探索方式可以得到alloc的流程:

alloc流程

  接下来我们再主要针对几个关键的函数进行分析:
方法_class_createInstanceFromZone
旧的方法在objc-class-old.mm:感兴趣的条友们可以自行研究一下

#   define WORD_MASK 7UL
/***********************************************************************
* _class_createInstanceFromZone.  Allocate an instance of the
* specified class with the specified number of bytes for indexed
* variables, in the specified zone.  The isa field is set to the
* class, C++ default constructors are called, and all other fields are zeroed.
在指定的区域创建一个指定类的实例,通过指定字节的变量
isa被设置到类,会调用C++的构造方法
**********************************************************************/
id 
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone)
{
    void *bytes;
    size_t size;

    // Can't create something for nothing
    if (!cls) return nil;

    // Allocate and initialize 创建
    size = cls->alignedInstanceSize() + extraBytes;

    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;

    if (zone) {
        bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        bytes = calloc(1, size);
    }

    return objc_constructInstance(cls, bytes);
}
// Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

现在使用的是在objc-runtime-new.mm文件中

调用:
OBJECT_CONSTRUCT_CALL_BADALLOC = 2
_class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);

/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
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;
// 在 __OBJC2__ 中已经忽略zone这个参数了,所以直接关注calloc函数
    if (zone) { 
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
// 申请内存空间,size为大小
        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;
// 返回C++构造类方法
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

// ********************** *************************
 uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }
// 最少16字节,16字节对齐
 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;
    }
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);
        }
    }
static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

\color{#000000}{内存字节对齐}:像对象、类等结构体的数据在内存中是连续的,数据有长有短,CPU为了提高速率,在读取数据的时是以\color{#ff0000}{块}的方式读取的,如果CPU不断的变换读取长度,对于性能势必会造成影响,所以就需要内存对齐。这就是典型的\color{#ff0000}{空间换取时间}。又因为数据是连续的,为了不造成读取数据的时候造成读取混乱,提高安全性,所以是以8字节的倍数对齐,但是有不能无限大,浪费空间,这就是著名的\color{#ff0000}{16字节对齐}
\color{#000000}{字节对齐算法}
(x + size_t(15)) & ~size_t(15)

x = 10 对应的二进制就是 0000 1010
(10 + 15) & ~15
25 & ~15
25 对应二进制0001 1001
15 对应二进制0000 1111
~15 对应二进制 1111 0000

25 & ~15 ===》 0001 1001
& 1111 0000
------------------------
0001 0000 = 16

init作用

  通过源码可以知道,init直接将alloc的对象直接返回,看似什么都没干,其实这是工厂设计模式,为了在继承该类的时候可以重写该类,提供构造方法的入口,并且赋初值。

+ (id)init {
    return (id)self;
}

总结:

  看起来有些乱,不过没关系,如果再结合类和对象的内存结构进行分析,将会得到一个更加清晰的思路和认识。

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

推荐阅读更多精彩内容