iOS底层原理探究01-alloc底层原理

一、底层研究的三种方法(查找底层源码出处)

  1. 下符号断点
    断点断住[LGPerson alloc];


    1624431409030.png

我们要研究alloc方法所以直接下一个alloc的符号断点(第二种方法详细讲了下符号断点的方法)、然后点击continue


1624431595445.png

这时候会进入alloc的符号断点,可以看到alloc方法是在libobjc.A.dylib库中
ps:也可以一开始就下alloc的符号断点,但是要先设置成Disable 等到了[LGPerson alloc];断点之后再把alloc的符号断点设置成Enable因为系统也会调用alloc方法我们要避免干扰准确的断住LGPerson的alloc

  1. 按住control + step into进入源码出处


    1624430823944.png

下断点断住[LGPerson alloc]、然后按住control + step into进入源码


1624430880268.png

可以看到底层实际上是进入了objc_alloc方法里


1624430955592.png
1624430998542.png

添加一个objc_alloc的符号断点,然后点击continue


1624431062056.png

可以看到objc_alloc方法是在libobjc.A.dylib库中

  1. 汇编跟流程查看实际调用的符号下符号断点


    1624432052303.png

设置Xcode ->Debug ->Debug Workflow->Always Show Disaassemdly显示汇编代码


1624432746381.png

可以看到实际调用的是objc_alloc符号 接下来下个对应的符号断点,就可以断点objc_alloc可以看到源码的出处

源码下载地址

苹果开源源码汇总: https://opensource.apple.com
这个地址⽤的更直接 https://opensource.apple.com/tarballs/

1624433023683.png

1624433441168.png

进入苹果开源网站点击macOS下的版本能看到苹果开源的源码库 搜索objc下载对应的源码

二、探究alloc源码


1624436326622.png

可以看到p1、p2、p3指向同一个对象(指向的内存地址也是一样的)三个指针自身的地址不同

PS:p1、p2、p3是在栈空间里的三个指针、他们的内存不同、他们指向堆空间里同一个地址LGPerson对象

接下来通过源码探究alloc 和 init做了些啥

1624438514478.png

在源码里搜索alloc的实现可以看到是在NSObject.mm(OC、C、C++混编)里调用了_objc_rootAlloc方法,一路找下去


1624438688956.png

_objc_rootAlloc调用了callAlloc


1624438702426.png

callAlloc里通过cls->ISA()->hasCustomAWZ()当前class是否有自定义的allocWithZone,如果没有自定义会调用_objc_rootAllocWithZone


1624439625387.png

而_objc_rootAllocWithZone最终会调用_class_createInstanceFromZone这个方法里最终生成对象,这里先理清楚流程后面会具体分析这个方法


1624439645268.png

callAlloc里如果自定义的allocWithZone,会判断当前类是否可以快速alloc如果没有会通过objc_msgSend调用allocWithZone:方法
如果可以快速alloc会通过objc_msgSend调用alloc方法
根据这个流程得到下面的流程图


alloc流程 (1).png

可以看到最终都是通过_class_createInstanceFromZone方法生成的对象接下来就来看下这个方法
/***********************************************************************

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

ctor 和 dtor 分别是什么呢?

bool hasCxxCtor() {
        ASSERT(isRealized());
        return cache.getBit(FAST_CACHE_HAS_CXX_CTOR);
    }
bool hasCxxCtor() {
        ASSERT(isRealized());
        return bits.data()->flags & RW_HAS_CXX_CTOR;
    }

ctor是判断当前class或者superclass 是否有.cxx_construct构造方法的实现。

bool hasCxxDtor() {
    ASSERT(isRealized());
    return cache.getBit(FAST_CACHE_HAS_CXX_DTOR);
}
bool hasCxxDtor() {
    ASSERT(isRealized());
    return bits.data()->flags & RW_HAS_CXX_DTOR;
}

dtor是判断判断当前class或者superclass 是否有.cxx_destruct析构方法的实现。

uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }
    // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }
    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;
    }

实例大小 instanceSize会存储在类的 isa_t结构体中,然后经过对齐最后返回(对象的内存大小8字节对齐,这样可以提高cpu效率)。

注意:Core Foundation 需要所有的对象的大小都必须大于或等于 16 字节。

在获取对象大小之后,直接调用calloc函数就可以为对象分配内存空间了。

关于calloc函数

The calloc( ) function contiguously allocates enough space for count objects that are size bytes of memory each and returns a pointer to the allocated memory. The allocated memory is filled with bytes of value zero. // calloc()函数连续地为count对象分配足够的空间,这些对象是内存的大小字节,并返回一个指向所分配内存的指针。分配的内存充满了值为零的字节。

申请完内存空间之后,还需要再初始化Isa指针。

obj->initInstanceIsa(cls, hasCxxDtor);
obj->initIsa(cls);

初始化Isa指针有这上面两个函数。

objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}

从上述源码中,我们也能看出,最终都是调用了initIsa函数,只不过入参不同。

objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#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
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    // 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结构体初始化的过程。
初始化之后obj被ruturn给外界使用alloc流程结束

待补充内容:
1、编译器优化
2、对象的字节对齐
3、内存对齐
4、对象大小的影响因素

参考资料:OC对象的一生

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

推荐阅读更多精彩内容