一:OC对象的原理(alloc init)

前言导入:

对于[[Class alloc] init], [Class new]相信大家都熟悉的不能再熟悉了吧,今天带大家深入了解一下alloc、init、new的底层原理吧

问题抛出:
    Person *p1 = [LGPerson alloc];
    Person *p2 = [p1 init];
    Person *p3 = [p1 init];
    LGNSLog(@"%@ - %p - %p",p1,p1,&p1);
    LGNSLog(@"%@ - %p - %p",p2,p2,&p2);
    LGNSLog(@"%@ - %p - %p",p3,p3,&p3);

对于以上的代码,大家先思考下,输出的语句是什么....?

<Person: 0x600002bdc130> - 0x600002bdc130 - 0x7ffeea65c178
<Person: 0x600002bdc130> - 0x600002bdc130 - 0x7ffeea65c170
<Person: 0x600002bdc130> - 0x600002bdc130 - 0x7ffeea65c168

对于以上的结果,我们要思考下为什么,得到的是这样的结果

分析:
1.输出的第一项是对象的描述;输出的第二项是对象指针指向的地址;输出的第三项是指针本身的地址
2.由输出结果可知,p2,p3的指针指向的是同一地址,也就是p1,那么猜想init实际上并没有做任何操作。


探究过程:

了解alloc底层的原理

首先我们commandalloc的代码,发现进入到

+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");

这不是我们想要的结果,苹果对于此api已经封装起来,无法进入到内部,所以我们只能去苹果开源找对应的api源码了

首先我们得确认找哪个方向的库文件,然后才能找到源码。

Step1:

首先我们在Person *p1 = [Person alloc];打下断点,然后打开Xcode->Debug->Debug WorkFlow->Alsways Show Disassembly,运行项目

0x10f16bed6 <+54>:  movq   0x5963(%rip), %rsi        ; (void *)0x000000010f171938: Person
    0x10f16bedd <+61>:  movq   %rsi, %rdi
    0x10f16bee0 <+64>:  callq  0x10f16de78               ; symbol stub for: objc_alloc
    0x10f16bee5 <+69>:  movq   %rax, -0x28(%rbp)
    0x10f16bee9 <+73>:  movq   -0x28(%rbp), %rax
    0x10f16beed <+77>:  movq   %rax, %rdi

我们截获到objc_alloc,接着我们打下符号断点,输入objc_alloc,接着运行,看到断点跑到

libobjc.A.dylib`objc_alloc:
->  0x7fff514122ac <+0>:  testq  %rdi, %rdi
    0x7fff514122af <+3>:  je     0x7fff514122cb            ; <+31>
    0x7fff514122b1 <+5>:  movq   (%rdi), %rax
    0x7fff514122b4 <+8>:  testb  $0x40, 0x1d(%rax)
    0x7fff514122b8 <+12>: jne    0x7fff5140a138            ; _objc_rootAllocWithZone
    0x7fff514122be <+18>: movq   0x389f4243(%rip), %rsi    ; "alloc"
    0x7fff514122c5 <+25>: jmpq   *0x36623fc5(%rip)         ; (void *)0x00007fff513f7780: objc_msgSend
    0x7fff514122cb <+31>: xorl   %eax, %eax
    0x7fff514122cd <+33>: retq  

从以上可知,我们需要在libojbc.A.dylib中去查找源码,

Step2:

苹果开源OpenSource中取查找我们的objc库,苹果开源链接,接着我们找到对应的objc,进行下载

image.png

下载完之后,配置好后,我们打开项目。

Step3:

在下载完的库代码中,一步一步执行断点,发现断点的踪迹:
初始断点处,项目继续执行

Person *objc1 = [Person alloc];

项目进入到alloc的执行代码块

+ (id)alloc {
    return _objc_rootAlloc(self);
}

接着来到_objc_rootAlloc执行代码块

id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

接着来到callAlloc执行代码块,是不是有点印象呀,之前我们就是依靠符号callAlloc断点来锁定的,这也和我们之前项目的代码断点不谋而合**

// 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_createInstanceFromZone:此处已经来到了最深处的核心实现代码,我们就这个核心代码来进行分析,来看看alloc 是怎么进行工作的

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 {
        // alloc 开辟内存的地方
        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);
}

image.png
image.png
alloc的流程总结:

alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone


_class_createInstanceFromZone核心代码的三部曲:

1.计算内存大小

 size_t size;
 size = cls->instanceSize(extraBytes);

2.申请开辟内存,返回地址指针

 obj = (id)calloc(1, size);

3.将isa指针和cls类进行关联

 obj->initInstanceIsa(cls, hasCxxDtor);

核心知识:

  • cls->instanceSize(extraBytes):运用16字节对齐的方式返回分配的字节数
 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);
}
  • calloc(1, size):运用16字节对齐的方式返回分配的字节数

command进入calloc方法,step_into进入查找到malloc_zone_calloc的方法;

libsystem_malloc.dylib`malloc_zone_calloc:
    0x7fff6a1b8ef6 <+0>:   pushq  %rbp
->  0x7fff6a1b8ef7 <+1>:   movq   %rsp, %rbp
    0x7fff6a1b8efa <+4>:   pushq  %r15
    0x7fff6a1b8efc <+6>:   pushq  %r14
    0x7fff6a1b8efe <+8>:   pushq  %r12
    0x7fff6a1b8f00 <+10>:  pushq  %rbx

进入苹果开源中找到对应的库文件,下载并打开,


image.png

获取到calloc的关键性核心代码

void *
calloc(size_t num_items, size_t size)
{
    void *retval;
    retval = malloc_zone_calloc(default_zone, num_items, size);
    if (retval == NULL) {
        errno = ENOMEM;
    }
    return retval;
}
void *
malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
    MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);

    void *ptr;
    if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
        internal_check();
    }

    ptr = zone->calloc(zone, num_items, size);
    
    if (malloc_logger) {
        malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
                (uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
    }

    MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
    return ptr;
}
  • initInstanceIsa:将指针和cls类进行关联:
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

未完待续》。。。

更新:

isa_t newisa(0);
首先我们来分析下isa结构体

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19

此结构体为联合体位域结构体,利用字节分配空间,大大地节省了isa指针的内存空间,即8*8的64位
其中最重要的要属"shiftcls",存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤来存储类指针

想当然的是,我们可以依赖于isa的字节,取出里面对应的33字节,来验证我们得出的结论。

验证:
image.png

step1:打印出objc1的内存分部

step2:打印出objc1的内存分部,以16进制的形式输出,第一个8位即isa指针,即isa指针中的bits

step3: bits先右移3位,即抹掉 uintptr_t nonpointer(1位)、 uintptr_t has_assoc( 1位 ) uintptr_t has_cxx_dtor (1位)

step4: 接着向右移动20位,抹掉后面的17位,再加上之前右移的3位,还原,这样就相当于吧shitlCls的前后部分都给抹掉了

step5: 最后将位置还原,这样即得到了shitCls的字节,然后我们输出得到了Class信息

step10:或者我们直接拿到isa的bits为,与我们的面具0x00007ffffffffff8ULL 进行与算法,这样也可以得到我们的shitCls的信息

得出结论:

所以我们的isa指针中与我们的类信息Class进行了关联


总结:

alloc的核心三部曲:

1.计算内存空间
2.分配内存空间,返回isa指针
3.将isa指针与Cls进行关联


课外进阶:

  • 结构体内存的计算:

结构体指针占8个字节,但是结构体本身占多少字节内存呢,这个就依赖于结构体的成员属性来决定了!!!!!

  • 规则如下:
    1:数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第 ⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存储。 min(当前开始的位置m n) m = 9 n = 4 9 10 11 12

    2:结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)

    3:收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤ 成员的整数倍.不⾜的要补⻬。

  • 数据类型占字节大小:


    image.png
  • example1:计算以下结构体占多少内存
struct LGStruct1 {
    double a;  
    char b;    
    int c;     
    short d;   
}struct1;

分析:

struct LGStruct1 {
    double a;    step1:  [0,1,2,3,4,5,6,7] 占8字节
    char b;    [8]  占1字节
    int c;     [9,10,11,12] ->[12,13,14,15] 占4字节
    short d;   [16,17]  占2字节
}struct1;

内部需要的大小为: 18
结构体整数倍: 3*8=24
但是最后要是最大成员的整数倍,也就是 24 字节

  • example2:计算以下结构体占多少内存
struct LGStruct2 {
    double a;   8 (0-7)
    int b;      4 (8 9 10 11)
    char c;     1 (12)
    char e ;    1 (13)
    char f;   1 (14)
    short d;    //2  (16,17)
}struct2;

内部需要的大小为: 18
结构体整数倍: 3*8 - 24
但是最后要是最大成员的整数倍,也就是 24 字节

  • example2:计算以下结构体占多少内存
struct LGStruct3 {
    double a;    step1:  [0,1,2,3,4,5,6,7] 占8字节
    char b;    [8]  占1字节
    struct LGStruct2 stru;  内部最大的成员占8字节,所以要从8的倍数开始,也就是 16 开始 [16 ...  33] 占18字节
    int c;     [36,37,38,39]  占4字节
    short d;   [40,41]  占2字节
}struct3;

等价于

struct LGStruct3 {
    double a;    step1:  [0,1,2,3,4,5,6,7] 占8字节
    char b;    [8]  占1字节
    struct LGStruct2 {
        double a;   8 (0-7) 从内部成员最大的整数倍开始[16,17,18,19,20,21,22,23]
        int b;      4 (24 25 26 27)
        char c;     1 (28)
        char e ;    1 (29)
        char f;   1 (30)
        short d;    2  (32,33)
    }struct2;
    int c;     [36,37,38,39]  占4字节
    short d;   [40,41]  占2字节
}struct3;

内部需要的大小为: 42
但是最后要是最大成员的整数倍,也就是 8*6= 48 >42字节
输出48

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