美文网首页
alloc与init探索

alloc与init探索

作者: 全球通_2017 | 来源:发表于2020-09-06 23:10 被阅读0次

知识点概要

引入
alloc干了什么?
为什么要有init?
为什么要有new?直接使用有何缺陷?
[NSObject alloc]为什么没有进入alloc方法?

引入

    Person *p1 = [[Person alloc]init];
    Person *p2 = [p1 init];
    Person *p3 = [p1 init];
    NSLog(@"%@ - %p - %p",p1,p1,&p1);
    NSLog(@"%@ - %p - %p",p2,p2,&p2);
    NSLog(@"%@ - %p - %p",p3,p3,&p3);

p1、p2、p3结果相同吗?&p1、&p2、&p3结果相同吗?
答案是:前者相同,后者不同,输出结果如下:


结果.png

我们来回顾C语言指针的知识:

    int temp = 3;//定一个int类型变量
    int *a = &temp;//声明一个int类型的指针,指向的地址是temp的内存地址
    //直接输出a和直接输出&temp的结果一样,都是temp的内存地址,而&a表示的是a的内存地址,因为指针类型也是一个变量,需要空间去存储
    printf("%p,%p,%p\n",a,&a,&temp);

那么,对于&p1、&p2、&p3结果不相同就可以明白了,话不多说,上图:


指针.png

至于p1、p2、p3调用init之后结果相同,是接下来要探索的问题。

alloc干了什么?

怎么查看?

1.下符号断点
1)我们知道会调用alloc方法,那么,先打上alloc符号断点


ialloc.png

可以看到,会调用NSObject类中的alloc方法,因为我们自定义的类继承自NSObject
2)在上一步中,可以看到jmp指令,会进入_objc_rootAlloc,所以,继续为_objc_rootAlloc打上断点


_objc_rootAlloc.png
3)继续按照之前的方式,符号断点_objc_rootAllocWithZone
_objc_rootAllocWithZone.png
4)可以看到会调用callq指令,调用了runtime中的instanceSize、calloc方法
calloc.png

调用instanceSize方法,计算需要开辟多大的内存,接着会调用calloc开辟这样一个大小的空间。最后,会调用initInstanceIsa方法,将对象与这个空间关联

2.根据符号断点查找库,然后,去苹果开源库下载源码,看源码,下面介绍三种方法查看。
1)第一种就是符号断点
2)在你要进入的位置,打上断点,然后,按住control键,点击调试按钮的“step into”,多点几次,会跳入另外一个断点

debug.png
step into.png
打上符号断点,点击下一步,如下图,我们就知道库名是objc
objc_alloc.png
3)打上断点,debug选为汇编模式,找到对应的方法objc_alloc,打符号断点
方法.png 汇编代码.png

总结:
1.通过符号断点,可以看到alloc的流程是:
alloc-->_objc_rootAlloc-->_objc_rootAllocWithZone-->retrun obj
_objc_rootAllocWithZone方法会调用核心的三个方法:
instanceSize:计算空间大小,并进行16字节对齐
calloc:根据大小,开辟一个这样的内存空间
initInstanceIsa:将开辟的内存空间与对象关联

注意:上述的三个核心方式其实不在_objc_rootAllocWithZone里面调用,而是在_class_createInstanceFromZone方法中调用的,原因稍后再说。

2.通过源码,调用流程如下:
alloc—>_objc_rootAlloc—>callAlloc—>_objc_rootAllocWithZone-->_class_createInstanceFromZone
可以看到,callAlloc、_class_createInstanceFromZone没有被调用。因为在编译时,编译器会根据指令进行优化,直接进入_objc_rootAllocWithZone


callAlloc.png

从代码及注释中都可以看到,调用时,会被优化
3.一个对象的大小,与其属性有关,属性越多,占用的空间越多,且按照16自字节的方式对齐(现在的新版本都是16字节对齐,之前是8字节对齐。为什么要对齐?读取按照一定字节读取,不去计算,会更加快速安全)。注意,没有属性的对象,最小占用16字节大小,

init干了什么?为什么要有init?

我们直接上源码


init.png
_objc_rootInit.png

可以看到,直接将对象返回,啥也没有做,不是我们想的会初始化空间值。

为什么要有init?

1.提供一个方法,对外给开发者使用,可以让开发者定义自己个性的初始化内容。
2.单一职责原则,与开辟内存空间功能分开

为什么要有new?有何缺陷?
new是一个类方法,调用简单,实际是alloc+init。

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

缺陷是:我们自定义了init,使用new不会调用我们自定义的init方法,造成一个对象的创建出问题。例如

- (instancetype)initWithName:(NSString *)name
{
    self = [super init];
    
    if (self) {
        self.className = name;
    }
    
    return self;
}

探讨[NSObject alloc]为什么没有进入alloc方法?

其实,无论是NSObject还是它的子类,调用alloc或者[[XXX alloc]init],优先调用以下代码:

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

// Calls [[cls alloc] init].
id
objc_alloc_init(Class cls)
{
    return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init];
}

那么,我么进入callAlloc方法看看

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

我们知道,现在是objc2.0,所以会进入第一个分支。首先不为nil,其次调用cls->ISA()->hasCustomAWZ(),看源码:

#if FAST_CACHE_HAS_DEFAULT_AWZ
    bool hasCustomAWZ() const {
        return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }
    void setHasDefaultAWZ() {
        cache.setBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        cache.clearBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }
#else
    bool hasCustomAWZ() const {
        return !(bits.data()->flags & RW_HAS_DEFAULT_AWZ);
    }
    void setHasDefaultAWZ() {
        bits.data()->setFlags(RW_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        bits.data()->clearFlags(RW_HAS_DEFAULT_AWZ);
    }
#endif

判断本类是有默认alloc/allocWithZone方法。很显然,NSObject实现了,所以,直接调用_objc_rootAllocWithZone方法创建了对象。当然了,如果是子类,而子类没有默认alloc/allocWithZone方法,则会走消息发送objc_msgSend,调用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*/);
}

走消息发送,最终会调用父类NSObject的alloc方法。此时,流程和之前流程一样,callAlloc->_objc_rootAllocWithZone

总结

1.为什么明明调用是alloc方法,实际调用的是objc_alloc方法?此时此刻,不要在心里一万头草泥马飘过,冷静下来。自己没有更改,源码中也没有更改,那么,答案就只有一种了,编译器做了优化。在这里可以在LLVM源码中找到答案,具体过程就不说了,太难了,知道就行。
2.在不考虑优化的情况下,alloc的详细流程如下:


alloc详细流程.png

相关文章

  • alloc与init探索

    知识点概要 引入alloc干了什么?为什么要有init?为什么要有new?直接使用有何缺陷?[NSObject a...

  • alloc init探索

    1、alloc init 首先用alloc生成一个LGPerson对象。在init另外的对象。 输出生成的对象信息...

  • alloc & init 探索

    首先创建一个对象 p1,p2,p3 打印结果结果 那么问题来了 alloc 做了什么?init 做了什么?上面的打...

  • iOS alloc & init 探索

    0x000 从哪里入手? 先看看main函数 0x001 初探? 为什么是 alloc init? alloc i...

  • iOS alloc & init 探索

    初探 1、新建一个Person 类 先思考打印情况 再运行 得到结果为 猜测:1、init 没有对内存进行任何...

  • alloc&init探索

    OC底层原理学习 alloc 初探 首先创建一个继承于NSObject的Person类带着以下几个疑问,查看打印结...

  • alloc&init探索

    main函数的加载流程 1.在int main前面打个断点 然后增加一个_objc_init的符号断点 记得关闭左...

  • alloc&init 探索

    alloc&init 探索 首先要明确alloc做了什么,init做了什么。 上方的p1/p2/p3经打印是一模一...

  • 1.对象原理探究

    alloc 探索 alloc 已经创建了对象 init alloc 实现 原理 源码实现 介绍三种方式 1、下断点...

  • OC底层原理系列文集

    1、对象底层之alloc&init&new源码分析+三种探索方式OC底层原理01-alloc流程探索[https:...

网友评论

      本文标题:alloc与init探索

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