OC底层探究之alloc流程

666 阅读4分钟

iOS启动流程

  • dyld start-----libsystem-----libdispatch----GCD环境准备------libObjc(_objc_init)

源码下载

alloc方法调试

方法1:下符号断点, 首先断点在alloc行, 同时下符号断点alloc(开始设置为disable,否则会先断住NSObject的alloc),commond + setp into调试获取 image.png image.png 结论:p1、p2、p3指向了同一片内存空间,它们自身指针地址不同,但在栈中是一片连续的内存空间 方法2:通过汇编调试 开启汇编调试模式 image.png control + step into(单步调试),确定下步调用函数objc_alloc image.png 下符号断点 image.png 再次运行程序,又可获得一个符号断点_objc_rootAllocWithZone,依次类推获取整个调用流程 image.png

源码分析

alloc入口

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

点击_objc_rootAlloc

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

点击callAlloc

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__ //判断是不是 objc2.0版本
    if (slowpath(checkNil && !cls)) return nil;
    //判断该类是否实现自自定义的 +allocWithZone,没有则进入if条件句
    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));
}

点击_objc_rootAllocWithZone

NEVER_INLINE
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,进入核心代码,三处核心点

  • 对象的大小由成员变量决定,开辟内存时,
    • 8字节对齐,取8的整数倍(原因:没有任何变量时,只存在isa,8个字节),cpu读取速度变快了,以空间换取时间
    • 干净内存ro data()->ro()->instanceSize
    • 内存对齐,即不浪费8字节空间,如int占4字节、char占1字节,则它们会被分配到同一8字节空间内
    • po就是以当前对象的description方法打印,浮点型对象没有description方法,打印不出来,可以用格式化e -f f -- 对象 打印
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);//核心1:编译时期确定了成员变量,计算大小,为开辟内存做准备
    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);//核心2:开辟内存空间,返回指针
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);//核心3:初始化指针,和类关联
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);//初始化isa指针和类关联
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

alloc流程总结

  • 编译器优化过程中,callAlloc可能会被优化掉 例如:寄存处X0 64位,W0 32位,存储字节数小的数据,编译器会优化为w寄存器
  • createInstance主要流程,根据变量大小开辟内存空间 -> 实例化isa(isa与类的绑定)
  • alloc流程指向_objc_rootAlloc或者objc_rootAlloc image.png
  • callAlloc走两次原因
  • 最终结论:程序在LLVM编译阶段就已经完成了objc_alloc的替换,这里不止替换掉alloc,还有很多函数releaseretainautorelease等等,至于为什么要hook掉这些函数,推测系统对对象的创建、释放做了很多监控。
  • 流程总结:alloc等一些方法在编译阶段LLVM会对alloc方法进行Hook,此函数会被替换成objc_alloc函数,这样在运行时声明一个对象LGPerson并且为其开辟内存空间的时候调用alloc函数,第一响应方法为objc_alloc,接着会进入callAlloc函数,第一次永远不满足此判断条件fastpath(!cls->ISA()->hasCustomAWZ())会触发((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc))objc_msgSend消息转发,为LGPerson对象发送了alloc消息,这个时候alloc函数才会真正被调用,然后进入_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone此方法里面做三件事:字节对齐开辟内存空间与对象绑定

未命名文件.jpg

init

- (id)init {
    return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}
  • init方法返回的是对象本身
  • init可以提供给开发者更多的自由去自定义 ,通过id实现强转,返回我们需要的类型

new

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

源码显示 走了callAlloc的方法流程,然后走了init方法 ,所以 new看做是alloc + init

总结

1630282953538.jpg