美文网首页
关于初始化

关于初始化

作者: 西二旗战神 | 来源:发表于2021-07-11 17:32 被阅读0次

    日常开发中,离不开对象的初始化,今天探索一下初始化时,系统做了什么;

    文章基于 objc-781.2 源码分析
    测试机: iPhone SE 2代

    先看一下打印的格式

    NSLog 在使用 %@ 打印的时候,会调用对象的 description 方法,本质上 oc 的对象是一个结构体;
    
    %p person1 是打印person1 对象地址
    %p &person1 是打印person1 指针本身的地址 
    

    老规矩,iOS代码探究离不开 Person,创建XSPersion,创建对应对象;

    
        XSPerson * person1 = [XSPerson alloc];
        XSPerson * person2 = [person1 init];
        XSPerson * person3 = [person1 init];
        XSPerson * person4 = [person2 init];
        XSPerson * person5 = [person4 init];
      
        NSLog(@"person1 %@ --- %p --- %p", person1, person1, &person1);
        NSLog(@"person2 %@ --- %p --- %p", person2, person2, &person2);
        NSLog(@"person3 %@ --- %p --- %p", person3, person3, &person3);
        NSLog(@"person4 %@ --- %p --- %p", person4, person4, &person4);
        NSLog(@"person5 %@ --- %p --- %p", person5, person5, &person5);
    
       TestInitProject[382:11760] person1 <XSPerson: 0x282168160> --- 0x282168160 --- 0x16f419b38
       TestInitProject[382:11760] person2 <XSPerson: 0x282168160> --- 0x282168160 --- 0x16f419b30
       TestInitProject[382:11760] person3 <XSPerson: 0x282168160> --- 0x282168160 --- 0x16f419b28
       TestInitProject[382:11760] person4 <XSPerson: 0x282168160> --- 0x282168160 --- 0x16f419b20
       TestInitProject[382:11760] person5 <XSPerson: 0x282168160> --- 0x282168160 --- 0x16f419b18
    
    
    可以看出person1、person2、person3、person4、person5 是指向同一份内存,但是person1、person2、person3、person4、person5指针本身的地址不一样;指针大小 8 字节,地址差 0x16f419b38依次差 0x8;

    以下是iOS内存管理示例,简述各大区域的管理方式;

    ios_memoryInfo.png
    下面看下,当我们调用 alloc 时,执行的代码流程
    1. 调用 _objc_rootAlloc 传参当前类
    + (id)alloc {
        return _objc_rootAlloc(self);
    }
    
    2. 调用 callAlloc 传参当前类和两个bool类型参数, false、true
    // 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*/);
    }
    
    3. callAlloc 方法中,经过断点执行的是 _objc_rootAllocWithZone方法;
    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));
    }
    
    
    #define fastpath(x) (__builtin_expect(bool(x), 1))
    #define slowpath(x) (__builtin_expect(bool(x), 0))
    

    该方法中的宏定义,声明在文件 Project Header -> objc-os.h 这个指令是gcc引入的,作用是允许程序员将最有可能执行的分支告诉编译器。

    xcode中对应配置:
    xcode_opt_level.png
    4. 我们继续看下 _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);
    }
    
    
    5. 来到本次主角方法 _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);
    }
    

    总结一下这个方法的主要步骤

    • ASSERT(cls->isRealized()); 用于容错

    • cls->instanceSize(extraBytes) 计算需要的内存大小

      • 内存计算遵循字节对齐,现在是16字节对齐,字节对齐的好处:
        • 内存由字节组成,但CPU在读取数据时,不是以字节为单位,而是以块为单位读存
        • 以块为单位读存,减少操作密度,提升性能
        • 方便CPU读取,提高效率,跨字节读取内存影响io吞吐效率
        • 两个对象的isa指针在内存挨着时,不对齐内存的跨字节读取容易造成访问混乱
    • 注意此时obj只是一个指针地址

    • 关联指针与class

      • 关联后,obj即为新开辟的对象,此时打印就可以看到 <xxx类型 : 0xxxxxxx地址>
       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);
      }
      
    6. 我们看下init时做了什么
    // Replaced by CF (throws an NSException)
    + (id)init {
        return (id)self;
    }
    
    - (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 就是返回当前的 self , init是个构造方法,才有工厂设计模式

    7. 我们在看下 new的实现
    + (id)new {
        return [callAlloc(self, false/*checkNil*/) init];
    }
    

    结合前面流程: new 相当于 [[XX类 alloc] init];

    8. 使用new 与使用 [[XX类 alloc] init] 创建对象的区别

    使用 new 走的

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

    如果我们重写了初始化 init时,使用 new不能走到我们自定义的初始化方法中;

    相关文章

      网友评论

          本文标题:关于初始化

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