美文网首页
Swift 构造器中属性初始化顺序及安全检查详解

Swift 构造器中属性初始化顺序及安全检查详解

作者: 烟影很美 | 来源:发表于2019-07-15 15:25 被阅读0次

    Swift初始化 编译的过程的四个安全检查

    1. 在调用父类初始化之前 必须给子类特有的属性设置初始值, 只有在类的所有存储属性状态都明确后, 这个对象才能被初始化
    2. 先调用父类的初始化方法, 再 给从父类那继承来的属性初始化值, 不然这些属性值 会被父类的初始化方法覆盖
    3. convenience 必须先调用 designated 初始化方法, 再 给属性初始值. 不然设置的属性初始值会被 designated 初始化方法覆盖
    4. 在第一阶段完成之前, 不能调用实类方法, 不能读取属性值, 不能引用self

    一般情况下, 我们能很好的根据这些原则写好构造器, 但是偶尔还是有些不太理解的事情发生. 以下示例:

    class AAA {}
    
    class BBB: AAA {
        var property:String {
            set {}
            get {
                return "tempString"
            }
        }
        
        override init() {
            self.property = "valueString"  // 报错'self' used in property access 'property' before 'super.init' call
            super.init()
        }
    }
    
    

    在上述代码中, self.property = "valueString" 一行报错: 'self' used in property access 'property' before 'super.init' call. 当然可以把这一行放在super.init()之后. 另外一种办法就是将计算属性property改为存储属性. 那么, 为什么存储属性可以计算属性就不行?

    下面代码中, 在setter方法中打断点, 查看调用堆栈, 发现其实际调用为BBB.property.setter(newValue="valueString", self=0x00006000002ff370)

    class AAA {}
    
    class BBB: AAA {
        var property:String {
            set {
                // 打断点
            }
            get {
                return "tempString"
            }
        }
        
        override init() {
            super.init()
        }
    }
    
    let obj = BBB.init()
    obj.property = "valueString"
    

    可以观察到, self作为参数使用, 违反了安全检查的第四条, 引用了self

    疑问:
    既然在第一阶段完成前不能引用self, 那为什么property为存储属性的时候可以在第一阶段使用selfself.property = "valueString"?

    Swift的编译过程如下:

    swift编译流程.jpg

    我们可以将Swift语言转为SIL再进行分析, 为了方便分析构造器中属性初始化和构造器外属性赋值的区别, 将swift代码简化如下:

    class AAA {
        init() {}
    }
    
    class BBB: AAA {
        
        var property:String
    
        override init() {
            self.property = "value1"
            super.init()
        }
        
        func setProperty() {
            self.property = "value2"
        }
    }
    
    let b = BBB.init()
    

    转换命令如下:

    swiftc -emit-sil main.swift
    

    得到的部分SIL如下:

    ......
    // BBB.init()
    sil hidden @$s4test3BBBCACycfc : $@convention(method) (@owned BBB) -> @owned BBB {
    // %0                                             // users: %9, %13, %2
    bb0(%0 : $BBB):
      %1 = alloc_stack $BBB, let, name "self"         // users: %17, %2, %19, %20
      store %0 to %1 : $*BBB                          // id: %2
      %3 = string_literal utf8 "value1"               // user: %8
      %4 = integer_literal $Builtin.Word, 6           // user: %8
      %5 = integer_literal $Builtin.Int1, -1          // user: %8
      %6 = metatype $@thin String.Type                // user: %8
      // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
      %7 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %8
      %8 = apply %7(%3, %4, %5, %6) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %11
      %9 = ref_element_addr %0 : $BBB, #BBB.property  // user: %10
      %10 = begin_access [modify] [dynamic] %9 : $*String // users: %11, %12
      store %8 to %10 : $*String                      // id: %11
      end_access %10 : $*String                       // id: %12
      %13 = upcast %0 : $BBB to $AAA                  // user: %15
      // function_ref AAA.init()
      %14 = function_ref @$s4test3AAACACycfc : $@convention(method) (@owned AAA) -> @owned AAA // user: %15
      %15 = apply %14(%13) : $@convention(method) (@owned AAA) -> @owned AAA // user: %16
      %16 = unchecked_ref_cast %15 : $AAA to $BBB     // users: %18, %21, %17
      store %16 to %1 : $*BBB                         // id: %17
      strong_retain %16 : $BBB                        // id: %18
      destroy_addr %1 : $*BBB                         // id: %19
      dealloc_stack %1 : $*BBB                        // id: %20
      return %16 : $BBB                               // id: %21
    } // end sil function '$s4test3BBBCACycfc'
    
    // String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
    sil [serialized] [always_inline] [readonly] [_semantics "string.makeUTF8"] @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String
    
    // BBB.setProperty()
    sil hidden @$s4test3BBBC11setPropertyyyF : $@convention(method) (@guaranteed BBB) -> () {
    // %0                                             // users: %9, %8, %1
    bb0(%0 : $BBB):
      debug_value %0 : $BBB, let, name "self", argno 1 // id: %1
      %2 = string_literal utf8 "value2"               // user: %7
      %3 = integer_literal $Builtin.Word, 6           // user: %7
      %4 = integer_literal $Builtin.Int1, -1          // user: %7
      %5 = metatype $@thin String.Type                // user: %7
      // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
      %6 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %7
      %7 = apply %6(%2, %3, %4, %5) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %9
      %8 = class_method %0 : $BBB, #BBB.property!setter.1 : (BBB) -> (String) -> (), $@convention(method) (@owned String, @guaranteed BBB) -> () // user: %9
      %9 = apply %8(%7, %0) : $@convention(method) (@owned String, @guaranteed BBB) -> ()
      %10 = tuple ()                                  // user: %11
      return %10 : $()                                // id: %11
    } // end sil function '$s4test3BBBC11setPropertyyyF'
    
    ......
    

    在代码中有明显的注释

    ......
    // BBB.init()
    ......
    // BBB.setProperty()
    ......
    

    BBB.init()部分, 对属性property赋值的代码为:

      .....
      %9 = ref_element_addr %0 : $BBB, #BBB.property  // user: %10
      %10 = begin_access [modify] [dynamic] %9 : $*String // users: %11, %12
      store %8 to %10 : $*String                      // id: %11
      end_access %10 : $*String 
      .....
    

    可以看到是通过指针%9将字符串%8保存起来的. 再看BBB.setProperty()部分:

      ......
      %8 = class_method %0 : $BBB, #BBB.property!setter.1 : (BBB) -> (String) -> (), $@convention(method) (@owned String, @guaranteed BBB) -> () // user: %9
      %9 = apply %8(%7, %0) : $@convention(method) (@owned String, @guaranteed BBB) -> ()
      ......
    

    %8为类方法BBB.property!setter, %0self, %7为赋值给属性的字符串.

    很明显, 在构造器内对存储属性的初始化代码符合安全检查, 而构造器外对存储属性的赋值与计算属性属性都是通过setter方法. 虽然同样都是self.xxxx = ..... 的调用方式, 但是编译结果却不一样. 有兴趣的同学可以看一看计算属性的SIL, 此文仅抛砖引玉.

    SIL官方文档: https://github.com/apple/swift/blob/master/docs/SIL.rst#witness-method
    Swift编译流程, 参考: https://www.jianshu.com/p/c27d312bccea

    另: 在指定构造器中, 先初始化本类所有的属性, 再调用super.init(). 在便利构造器中, 需要先调用指定构造器, 再修改属性值. 即实例的创建是在指定构造器中一开始完成的. 参考BBB.init()中部分代码:

    %1 = alloc_stack $BBB, let, name "self"         // users: %17, %2, %19, %20
    

    相关文章

      网友评论

          本文标题:Swift 构造器中属性初始化顺序及安全检查详解

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