美文网首页
iOS底层探索的流程 & alloc以及init初探

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

作者: spyn_n | 来源:发表于2021-06-07 00:01 被阅读0次

序言

  探索底层,我们开始从平时最经常用的创建对象的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;
}

总结:

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

相关文章

网友评论

      本文标题:iOS底层探索的流程 & alloc以及init初探

      本文链接:https://www.haomeiwen.com/subject/kqedlltx.html