美文网首页
iOS底层探索 --- OC对象原理(中)

iOS底层探索 --- OC对象原理(中)

作者: Jax_YD | 来源:发表于2021-06-24 09:57 被阅读0次
    image
    我们在上一篇文章中分析了alloc的流程,后来发现其中有一些细节没有讲清楚,这里我们再来探索一下我们之前没有探索到的地方。

    这一片文章将为大家介绍一下内容:

    1、alloc内容补充

    2、内存对齐原则

    3、结构体内存对齐练习

    4、一些控制台打印小技巧


    1、 alloc内容补充

    1.1、 上一篇内容回顾

    我们在iOS底层探索 --- OC对象原理(上)中创建了一个Person对象,并且撰写了这样一段代码:

    Person *p = [Person alloc];
    

    我们在探索源码的时候,进入的是_objc_rootAlloc

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

    这是我们正常的源码探索流程,并没有什么错。


    1.2、 问题的发现

    依然是这个地方下断点:

    image
    通过汇编代码,我发现一个问题:
    image
    大家看到没,马上要进入的是objc_alloc,并不是_objc_rootAlloc

    这就奇怪了,按理说进入的应该是_objc_rootAlloc才对。


    1.3、源码继续探索

    既然动态调试的时候,汇编代码告诉我们首先进入的是objc_alloc,那我们就在源码中搜寻一下objc_alloc。(cmd + shift + O

    image

    那我们就在objc_alloc_objc_rootAlloc同时打上断点,运行看一下,哪个断点先来。(注意,测试之前先clean,避免缓存造成影响)

    这里有一点,大家要注意,两个方法都会调用callAlloc,但是传入的参数不同:

    /******** _objc_rootAlloc ********/
    id
    _objc_rootAlloc(Class cls)
    {
       return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
    }
    
    
    /******** objc_alloc ********/
    // Calls [cls alloc].
    id
    objc_alloc(Class cls)
    {
       return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
    }
    
    image
    可以看到首先进入的是objc_alloc。记下来进入callAlloc,在该方法中打上断点:
    image
    继续运行,你会发现,它会跳过所有if判断,进入到最后一行,objc_msgSend:
    image

    接下来才会进入我们在iOS底层探索 --- OC对象原理(上)中探索的流程。

    虽然动态调试的时候,流程确实是这样的;但是问题就来了,这个流程不符合我们的尝试,因为alloc函数里面是_objc_rootAlloc,可是什么时候变成了objc_alloc呢?

    这个给我的第一印象是,是不是苹果在某个地方进行了方法交换?或者是HOOK
    有了这个想法,那接下来就是去源码里面寻找了。于是我找到了这个:

    image

    继续跟进,发现是在_read_images里面调用的:

    image
    那么我们就对_read_images进行搜索,在其被调用的地方,埋下断点,然后运行工程,然后发现了一些熟悉的内容:
    image
    看到没,这是dyld的加载,也就是说,在dyld加载的时候,工程就对alloc进行了替换,其IMP指向了objc_alloc;在执行完objc_alloc之后,走的是消息发送流程objc_msgSend
    对这一块不太了解的同学,可以查阅:
    iOS底层探索 --- dyld加载流程
    iOS底层探索 ---Runtime(一)--- 基础知识

    总结起来就是:
    alloc在执行的时候,会先走一个特殊流程(objc_alloc),然后通过消息发送,进入普通的alloc流程。

    这里主要是因为,alloc是对内存的操作,苹果对于这样的操作要做一定的管控。


    2、内存对齐原则

    1. 数据成员对齐规则:结构体(struct)(或联合(union))的成员,第一个数据成员放在offset0的地方,以后每个数据成员存储的起始位置要从该成员大小(当前成员)或者成员的子成员大小(只要该成员有子成员,比如说数组结构体等)的整数倍开始。(比如:int4字节,则要从4的整数倍地址开始存储。)
    2. 结构体作为成员:如果一个结构体里面有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(比如:struct a里面存有struct bb里面有charintdouble等元素,那么b应该从8的整数倍开始存储。)
    3. 收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
    C OC 32位 64位
    bool BOOL(64位) 1 1
    signed char (__signed char)int8_t、BOOL(32位) 1 1
    unsigned char Boolean 1 1
    short Int16_t 2 2
    unsigned short unichar 2 2
    int int32_t NSInteger(32位)、boolean_t(32位) 4 4
    unsigned int boolean_t(64位)、NSUInteger(32位) 4 4
    long NSInteger(64位) 4 8
    unsigned long NSUInteger(64位) 4 8
    long long int64_t 8 8
    float CGFloat(32位) 4 4
    double CGFloat(64位) 8 8

    3、结构体内存对齐练习

    • 练习题(1):
    struct JaxStruct1 {
        double a;       // 8    [0 7]
        char b;         // 1    [8]
        int c;          // 4    (9 10 11 [12 13 14 15]
        short d;        // 2    [16 17] 24
    }struct1;
    
    image
    • 练习题(2):
    struct JaxStruct2 {
        double a;       // 8    [0 7]
        int b;          // 4    [8 9 10 11]
        char c;         // 1    [12]
        short d;        // 2    (13 [14 15] 16
    }struct2;
    
    image
    • 练习题(3):
    struct JaxStruct3 {
        double a;                   // 8    [0 7]
        int b;                      // 4    [8 9 10 11]
        char c;                     // 1    [12]
        short d;                    // 2    (13 [14 15]
        int e;                      // 4    [16 17 18 19]
        struct JaxStruct1 str;      // 24   (20 21 22 23 [sizeof(struct1)]
    }struct3;
    
    image

    4、一些控制台打印小技巧

    我们在Xcode使用控制台打印一些数据的时候,有使用过这样一种形式的:

    4.1、打印对象内存数据

    x/nuf <address>
    

    • n:表示要显示的内存单元的个数。

    • u:表示一个地址单位的长度:
      • b:表示单字节
      • h:表示双字节
      • w:表示四字节
      • g:表示八字节

    • f:表示显示方式,可取如下值:
      • x:按十六进制格式显示变量
      • d:按十进制格式显示变量
      • u:按十进制格式显示无符号整形
      • o:按八进制格式显示变量
      • t:按二进制格式显示变量
      • a:按十六进制格式显示变量
      • i:指令地址格式
      • c:按字符格式显示变量
      • f:按浮点数格式显示变量

    • 一般来讲,省略f,默认的是16进制
      image

    4.2、打印对象成员变量

    • 如果成员变量是NSString,或者Int等类型,使用po执行就可以打印:
    $ po <address>
    
    • 如果想要打印出浮点数,可以这样打印:
    $ p/f <address>
    或者
    $ e -f f -- <address>
    
    image

    相关文章

      网友评论

          本文标题:iOS底层探索 --- OC对象原理(中)

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