Swift类、对象、属性

1,004 阅读3分钟

Swift编译简介

SIL介绍

  • SIL是Swift Intermediate Language的简写,SIL会对Swift进行高级别的语意分析和优化,我们通过解读SIL代码就能去了解Swift背后的一些实现细节,帮助我们理解一些问题。
  • IOS开发语言不管OC还是Swift后端都是通过LLVM编译的 image.png
  • OC通过Clang编译器前端,编译成IR,然后再生成目标文件.o
  • Swift是通过Swift编译器前端,编译成IR,再生成目标文件.o
  • Swift编译过程都经历了哪些步骤 image.png
  • Swift语言也是基于LLVM架构的,你可以看到与现有Objective-C语言有很多相似之处,有相同的后端结构,前端的工作流程也基本类似,词法分析,标记,构建AST,类型检查,主要区别在于生成IR之前,AST之后,增加了SIL,它是AST和LLVM IR之间的另一种中间代码表示形式。
    这么做的目的是希望弥补一些Clang编译器的缺陷,如无法执行一些高级分析,可靠的诊断和优化,而AST和LLVM IR都不是合适的选择。因此,SIL应运而生,用来解决现有的缺陷。
  • 通过命令查看swiftc能做什么样的事情 image.png

SIL分析

  • 在main.swift中输入如下代码
class XQTeacher {
    var age:Int = 18
    var name:String = "xq"
}

var t = XQTeacher()
  • 通过命令swiftc -dump-ast main.swift查看语法树 image.png

  • 通过命令swiftc -emit-sil main.swift >> ./main.sil生成main.sil并用VSCode打开它,我们来研究它的main函数

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main1tAA9XQTeacherCvp          // id: %2
  %3 = global_addr @$s4main1tAA9XQTeacherCvp : $*XQTeacher // user: %7
  %4 = metatype $@thick XQTeacher.Type            // user: %6
  // function_ref XQTeacher.__allocating_init()
  %5 = function_ref @$s4main9XQTeacherCACycfC : $@convention(method) (@thick XQTeacher.Type) -> @owned XQTeacher // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick XQTeacher.Type) -> @owned XQTeacher // user: %7
  store %6 to %3 : $*XQTeacher                    // id: %7
  %8 = integer_literal $Builtin.Int32, 0          // user: %9
  %9 = struct $Int32 (%8 : $Builtin.Int32)        // user: %10
  return %9 : $Int32                              // id: %10
} // end sil function 'main'
  • @main这里标识我们当前main.swift 的入口函数,SIL中的标识符名称以@作为前缀
  • %0,%1...在SIL也叫做寄存器,这里我们可以理解为我们日常开发中的常量,一旦赋值之后就不可以再修改,如果SIL中还要继续使用,那么就不断的累加数字。同时这里所说的寄存器是虚拟的,最终运行到我们的机器上,会使用真的寄存器。
  • alloc_gobal创建一个全局变量
  • global_ addr拿到全局变量的地址,赋值给%3
  • metatype拿到XQTeacher的Metadata赋值给%4
  • 将 allocating_init的函数地址赋值给%5
  • apply调用 allocating_init,并把返回值给%6
  • 将%6的值存储到%3(也就是我们刚刚创建的全局变量的地址)
  • 构建Int,并return
  • XQTeacher.__allocating_init()的实现
// XQTeacher.__allocating_init()
sil hidden [exact_self_class] @$s4main9XQTeacherCACycfC : $@convention(method) (@thick XQTeacher.Type) -> @owned XQTeacher {
// %0 "$metatype"
bb0(%0 : $@thick XQTeacher.Type):
  %1 = alloc_ref $XQTeacher                       // user: %3
  // function_ref XQTeacher.init()
  %2 = function_ref @$s4main9XQTeacherCACycfc : $@convention(method) (@owned XQTeacher) -> @owned XQTeacher // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned XQTeacher) -> @owned XQTeacher // user: %4
  return %3 : $XQTeacher                          // id: %4
} // end sil function '$s4main9XQTeacherCACycfC'

通过符号断点调试

  • 在代码新增符号断点_allocing_init ,运行代码 image.png
  • 往下执行到swift_allocObject image.png

源码调试

  • 在编译好的源码的_swift_allocObject_方法加入一个断点 image.png
  • REPL(命令交互行)中编写代码(也可以拷贝) image.png
  • 查看左上角的调试数据其中requiredSize是分配的实际内存大小,为40
  • requiredAlignmentMask是swift中的字节对齐长度,为8

_swift_allocObject_

  • _swift_allocObject_的源码实现
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));//分配内存

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  new (object) HeapObject(metadata);//初始化实例对象

  // If leak tracking is enabled, start tracking this object.
  SWIFT_LEAKS_START_TRACKING_OBJECT(object);

  SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);

  return object;
}
  • swift_slowAlloc分配内存
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
  void *p;
  // This check also forces "default" alignment to use AlignedAlloc.
  if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__)
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size);
#endif
  } else {
    size_t alignment = (alignMask == ~(size_t(0)))
                           ? _swift_MinAllocationAlignment
                           : alignMask + 1;
    p = AlignedAlloc(size, alignment);
  }
  if (!p) swift::crash("Could not allocate memory.");
  return p;
}
  • new (object) HeapObject(metadata);初始化实例对象
struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *metadata;
  // InlineRefCounts refCounts
  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;

#ifndef __swift__
  HeapObject() = default;

  // Initialize a HeapObject header as appropriate for a newly-allocated object.
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
  ......
}
  • 实例对象是以HeapObject来创建的,HeapObject里面有一个HeapMetadata类型的metadata,还有一个引用计数refCounts
using HeapMetadata = TargetHeapMetadata<InProcess>;
struct TargetHeapMetadata : TargetMetadata<Runtime> {
  using HeaderType = TargetHeapMetadataHeader<Runtime>;

  TargetHeapMetadata() = default;
  constexpr TargetHeapMetadata(MetadataKind kind)
    : TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
  constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
    : TargetMetadata<Runtime>(isa) {}
#endif
};
  • 其内部结构如图 image.png
  • 得到最终Swiftmetadata结构体的构成
struct swift_class_t: NSObject{
    void *kind;//相当于OC中的isa,kind的实际类型是unsigned long
    void *superClass;
    void *cacheData;
    void *data;
    uint32_t flags; //4字节
    uint32_t instanceAddressOffset;//4字节
    uint32_t instanceSize;//4字节
    uint16_t instanceAlignMask;//2字节
    uint16_t reserved;//2字节
    
    uint32_t classSize;//4字节
    uint32_t classAddressOffset;//4字节
    void *description;
    ...
}

Swift属性

存储属性

  • 存储属性要么用let修饰的常量,要么是var修饰的变量
class XQTeacher {
    var age:Int = 18
    var name:String = "xq"
}
  • 上面的两个属性就是变量存储属性,我们也可以通过SIL查看
class XQTeacher {
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @_hasStorage @_hasInitialValue var name: String { get set }
  @objc deinit
  init()
}

计算属性

  • 计算属性:是指不占用内存空间,本质是set/get方法的属性
  • 通过打印下面这个类的实例对象内存大小,打印结果是24,HeapObject默认有16个字节的内存,再加上width占8个字节
class Square {
    var width:Double = 10
    var area:Double {
        get {
            Double(width * width)
        }
        set {
           width = sqrt(newValue)
        }
    }
}
print(class_getInstanceSize(Square.self))
  • 查看sil文件,看到存储属性只有width,而area的本质就是setget方法
class Square {
  @_hasStorage @_hasInitialValue var width: Double { get set }
  var area: Double { get set }
  @objc deinit
  init()
}

属性观察者

  • 属性观察者就是willSetdidSet
class XQTeacher {
    var name:String = "xq" {
        willSet {
            print("newValue:\(newValue)")
        }
        didSet {
            print("oldValue:\(oldValue)")
        }
    }
}
var t = XQTeacher()
t.name = "dp"
  • 代码执行结果,可以知道在新值存储之前调用了willSet,新值存储之后调用了didSet
newValue:dp
oldValue:xq
  • 观察sil,验证上文的结论 image.png
  • 再来看下一个情况,在init方法里面调用set方法
class XQTeacher {
    var name:String = "xq" {
        willSet {
            print("newValue:\(newValue)")
        }
        didSet {
            print("oldValue:\(oldValue)")
        }
    }
    init() {
        self.name = "test"
    }
}
var t = XQTeacher()
  • 发现它并不会触发属性观察者,原因是我们在初始化的时候设置属性值的时候,会先调用memset清理内存空间,因为这块内存可能有残留的脏数据,此时我们在didSet里面调用其他属性时有可能其他属性内存未被分配完成会访问一片未知的内存空间,Swift是一门安全性的语言这种操作是不允许的

  • 那些地方可以添加属性观察者

  • 类中定义的存储属性

class XQTeacher {
    var name:String = "xq" {
        willSet {
            print("newValue:\(newValue)")
        }
        didSet {
            print("oldValue:\(oldValue)")
        }
    }
}
  • 继承的存储属性
class XQSpecialTeacher: XQTeacher {
    override var name: String {
        willSet {
            print("newValue:\(newValue)")
        }
        didSet {
            print("oldValue:\(oldValue)")
        }
    }
}
  • 继承的计算属性
class XQTeacher {
    var age:Int = 18
    
    var age2:Int {
        get {
            return age
        }
        set {
            age = newValue
        }
    }
}

class XQSpecialTeacher: XQTeacher {
    override var age2: Int {
        willSet {
            print("override newValue:\(newValue)")
        }
        didSet {
            print("override oldValue:\(oldValue)")
        }
    }
}
  • 父类和子类都实现了属性观察者,它的调用顺序
class XQTeacher {
    var age:Int = 18 {
        willSet {
            print("newValue:\(newValue)")
        }
        didSet {
            print("oldValue:\(oldValue)")
        }
    }
    
    var age2:Int {
        get {
            return age
        }
        set {
            age = newValue
        }
    }
}

class XQSpecialTeacher: XQTeacher {
    override var age: Int {
        willSet {
            print("override newValue:\(newValue)")
        }
        didSet {
            print("override oldValue:\(oldValue)")
        }
    }
}
  • 调用顺序如下,子类和父类都实现了属性观察者,会先调用子类的willSet,再调用父类的willSet,再调用父类的didSet,最后调用子类的didSet
override newValue:99
newValue:99
oldValue:18
override oldValue:18
  • 我们在子类的init方法里面调用set方法会不会触发属性观察者呢
class XQSpecialTeacher: XQTeacher {
    override var age: Int {
        willSet {
            print("override newValue:\(newValue)")
        }
        didSet {
            print("override oldValue:\(oldValue)")
        }
    }
    override init() {
        super.init()
        self.age = 66
    }
}
  • 通过打印结果可以看到,是会调用的,原因是调用了父类的init已经有默认值了即属性已经初始化完成了,是可以调用属性观察者的。

延迟存储属性

  • 1、使用lazy修饰的存储属性
class XQTeacher {
   lazy var age:Int = 18
}
  • 2、延迟属性必须有一个默认的初始值,没有初始值会报错 image.png

  • 3、延迟存储在第一次访问的时候才被赋值 image.png

  • 4、延迟存储属性并不能保证线程安全

  • 5、延迟存储属性对实例对象大小的影响,我们不使用延迟存储属性的时候内存大小为24,使用之后为32 image.png

  • 通过sil分析延迟存储属性

类型属性

  • 类型属性属于这个类本身,不管有多少实例,类型属性只有一份,我们使用static来声明一个类型属性
class XQTeacher {
    static var age : Int = 18
}
var t = XQTeacher()
XQTeacher.age = 20
  • 通过查看sil文件,可以看到多出了一个全局变量,类型属性其实是一个全局变量
// static XQTeacher.age
sil_global hidden @$s4main9XQTeacherC3ageSivpZ : $Int
  • 查看到main函数获取age的方法,调用的是XQTeacher.age.unsafeMutableAddressor image.png
  • XQTeacher.age.unsafeMutableAddressor里面调用了globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0
// XQTeacher.age.unsafeMutableAddressor
sil hidden [global_init] @$s4main9XQTeacherC3ageSivau : $@convention(thin) () -> Builtin.RawPointer {
bb0:
  %0 = global_addr @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0 : $*Builtin.Word // user: %1
  %1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer // user: %3
  // function_ref globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0
  %2 = function_ref @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0 : $@convention(c) () -> () // user: %3
  %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $()
  %4 = global_addr @$s4main9XQTeacherC3ageSivpZ : $*Int // user: %5
  %5 = address_to_pointer %4 : $*Int to $Builtin.RawPointer // user: %6
  return %5 : $Builtin.RawPointer                 // id: %6
} // end sil function '$s4main9XQTeacherC3ageSivau'
  • globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0的实现
// globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0
sil private @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0 : $@convention(c) () -> () {
bb0:
  alloc_global @$s4main9XQTeacherC3ageSivpZ       // id: %0 
  %1 = global_addr @$s4main9XQTeacherC3ageSivpZ : $*Int // user: %4 获取到age的地址
  %2 = integer_literal $Builtin.Int64, 18         // user: %3  初始化的值18
  %3 = struct $Int (%2 : $Builtin.Int64)          // user: %4
  store %3 to %1 : $*Int                          // id: %4  将%3的值放到地址中
  %5 = tuple ()                                   // user: %6
  return %5 : $()                                 // id: %6
} // end sil function 'globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0'
  • 通过汇编调试查看 image.png
  • 继续执行 image.png
  • 可以看到会调用swift_once,表示全局变量只会初始化一次,对应sil中的builtin "once"
  • 通过源码查看swift_once 的实现,它的底层是调用一个gcd的单次执行函数
/// Runs the given function with the given context argument exactly once.
/// The predicate argument must point to a global or static variable of static
/// extent of type swift_once_t.
void swift::swift_once(swift_once_t *predicate, void (*fn)(void *),
                       void *context) {
#if defined(__APPLE__)
  dispatch_once_f(predicate, context, fn);
#elif defined(__CYGWIN__)
  _swift_once_f(predicate, context, fn);
#else
  std::call_once(*predicate, [fn, context]() { fn(context); });
#endif
}

实现单列

  • 通过上文对static的了解,我们可以用static来实现一个单列
class XQTeacher {
    static let sharedInstance = XQTeacher()
    
    private init() {
        
    }
}
var t = XQTeacher.sharedInstance