Swift 进阶系列 - 01 类与结构体(上)

330 阅读9分钟

1. 类与结构体的异同:

类的定义

class PersonClass {
    /* 1.类中可以定义存储值的属性 */
    // 定义String 类型属性 name,初始值为 “Onion_Knight”
    var name: String = "Onion_Knight"
    // 定义Int 类型属性 age,初始值为 25
    var age: Int = 25
  
    /* 2.类中可以定义方法 */
    // 定义方法 run
    func run() {
        print("Person is running")
    }
  	
    /* 3.类中可以定义初始化器 */

    // 初始化器 (Initializer)

    // 必要初始化器 (Required Initializer)
    required init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    // 便捷初始化器 (Convenience Initializer)
    convenience init(name: String) {
        let defaultAge: Int = 8
        self.init(name: name, age: defaultAge)
    }
  	
    // TODO: 定义下标,extension,conform to protocol
}

结构体的定义

struct PersonStruct {
    /* 1.结构体中可以定义存储值的属性 */
    var name: String = "Onion_Knight"
    var age: Int = 25
    
    /* 2.结构体中可以定义方法 */
    func run() {
        print("Person is running")
    }
    
    /* 3.结构体中可以定义初始化器, 但这里与类有所不同 */
    // 初始化器 (Initializer)
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
  
    // TODO: 定义下标,extension,conform to protocol   
}

这里我们更加关注结构体与类的不同点:

**类是引用类型,结构体是值类型。**因此引用计数是对于类来说的。

类有继承的特性, 而结构体没有。

类型转换可以在运行时检查和解释类实例的类型,而结构体不可以。

类有析构函数,而结构体不可以定制析构函数。

针对类的引用类型, 结构体的值类型:

// 快捷打印指针
func printPointer<T>(_ ptr: UnsafePointer<T>) {
    print(ptr)
}

var personClassInstance = PersonClass(name: "OKClass", age: 18)
var personClassInstance_1 = personClassInstance

var personStructInstance = PersonStruct(name: "OKStruct", age: 19)
var personStructInstance_1 = personStructInstance

personClassInstance.age = 0
personStructInstance.age = 0

print(personClassInstance_1.age) // 0
print(personStructInstance_1.age) // 19

/* 通过 frame variable -L 查看各实例内存信息如下
(lldb) frame variable -L personClassInstance
scalar: (Swift_Programming.PersonClass) personClassInstance = 0x000000010041b3a0 {
0x000000010041b3b0:   name = "OKClass"
0x000000010041b3c0:   age = 0
}
(lldb) frame variable -L personClassInstance_1
scalar: (Swift_Programming.PersonClass) personClassInstance_1 = 0x000000010041b3a0 {
0x000000010041b3b0:   name = "OKClass"
0x000000010041b3c0:   age = 0
}
(lldb) frame variable -L personStructInstance
0x0000000100008380: (Swift_Programming.PersonStruct) personStructInstance = {
0x0000000100008380:   name = "OKStruct"
0x0000000100008390:   age = 0
}
(lldb) frame variable -L personStructInstance_1
0x0000000100008398: (Swift_Programming.PersonStruct) personStructInstance_1 = {
0x0000000100008398:   name = "OKStruct"
0x00000001000083a8:   age = 19
}
*/

修改personClassInstance的age,personClassInstance_1 的age也发生了改变,而struct不同,正是因为类为引用类型,而结构体是值类型。而通过frame variable -L 获取帧栈信息更是切实印证了这一点。

也正是因为类为引用类型,结构体是值类型,一般情况下(不考虑全局/静态等特殊情况),类实例存储在堆区,结构体实例存储在栈区。

由于堆区空间的创建和销毁要比栈区更耗费时间,因此在不需要类的特性时,应当尽量使用结构体替代类以优化性能。(详见结构体与类的性能测试)。

2. 类的初始化器:

class PersonClass {
        
    var name: String = "Onion_Knight"
    var age: Int = 25
    
    // 指定初始化器 (Designated Initializer) + 可失败 ?
    init?(name: String, age: Int, minAge: Int) {
        if minAge > age {
            return nil
        }
        self.name = name
        self.age = age
    }
    
    // 指定初始化器 (Designated Initializer)
    required init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    // 便捷初始化器 (Convenience Initializer) + 必需 + 可失败
    convenience required init?(name: String) {
        let defaultAge: Int = 8
        self.init(name: name, age: defaultAge)
    }
    
    // 便捷初始化器 (Convenience Initializer)
    convenience init(age: Int) {
        let defaultName: String = "OK君"
        self.init(name: defaultName, age: age)
    }
    
}

​ 初始化器大体分为两类:指定初始化器便捷初始化器

  • 指定初始化器:

    A: 必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性

    都要初始化完成。

    B: 必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如

    果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖。

  • 便捷初始化器:

    A: 必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括 同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖。

    初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用 self 作为值。

    在初始化器上还可以通过添加required或者?让初始化器成为 可失败初始化器 和 * 必要初始化器*。

    -- 可失败初始化器: 意味着当前因为参数的不合法或者外部条件 的不满足,存在初始化失败的情况。这种 Swift 中可失败初始化器写 return nil 语句, 来表明可失败初始化器在何种情况下会触发初始化失败。

    -- 必要初始化器: 在类的初始化器前添加 required 修饰符来表明所有该类的子类都必须 实现该初始化器

3. 类的生命周期 -- 初始化:

iOS开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的。

OC 通过 clang 编译器,编译成 IR,然后再生成可执行文件 .o (这里也就是我们的机器码)

Swift 则是通过 Swift 编译器编译成 IR,然后在生成可执行文件。

如下图所示:

0.png Swift中具体过程如下:

Swift 代码 -> Parse 语法分析为 AST (抽象语法树) -> Sema 语义分析 -> 生成 SIL (Swift 中间语言)-> Raw SIL -> SILOpt Canonical SIL (优化过) -> IRGen -> LLVM IR -> Machine Code

1.png

// 分析输出AST
swiftc main.swift -dump-parse
// 分析并且检查类型输出AST 
swiftc main.swift -dump-ast
// 生成中间体语言(SIL),未优化 
swiftc main.swift -emit-silgen
// 生成中间体语言(SIL),优化后的 
swiftc main.swift -emit-sil
// 生成LLVM中间体语言 (.ll文件) 
swiftc main.swift -emit-ir
// 生成LLVM中间体语言 (.bc文件) 
swiftc main.swift -emit-bc
// 生成汇编 
swiftc main.swift -emit-assembly
// 编译生成可执行.out文件 
swiftc -o main.o main.swift

SIL 分析:

有如下swift源码(main.swift)

class SimpleClass {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let simple = SimpleClass(name: "OK", age: 18)

通过swiftc main.swift -emit-sil > ./main.sil 生成.sil文件

详见 SIL语言官方文档

分析类的初始化流程 注意到如下代码 (__allocating_init)

// SimpleClass.__allocating_init(name:age:)
sil hidden [exact_self_class] @$s4main11SimpleClassC4name3ageACSS_SitcfC : $@convention(method) (@owned String, Int, @thick SimpleClass.Type) -> @owned SimpleClass {
// %0 "name"                                      // user: %5
// %1 "age"                                       // user: %5
// %2 "$metatype"
bb0(%0 : $String, %1 : $Int, %2 : $@thick SimpleClass.Type):
  %3 = alloc_ref $SimpleClass                     // user: %5
  // function_ref SimpleClass.init(name:age:)
  %4 = function_ref @$s4main11SimpleClassC4name3ageACSS_Sitcfc : $@convention(method) (@owned String, Int, @owned SimpleClass) -> @owned SimpleClass // user: %5
  %5 = apply %4(%0, %1, %3) : $@convention(method) (@owned String, Int, @owned SimpleClass) -> @owned SimpleClass // user: %6
  return %5 : $SimpleClass                        // id: %6
} // end sil function '$s4main11SimpleClassC4name3ageACSS_SitcfC'

这里的 SimpleClass.Type 作用类似于 OC 中的 isa.

在代码中通过汇编跟进, 发现

2.png

继续跟进:

3.png

作为一个纯粹的swift类(没有继承关系),主要流程:__allocating_init -> swift_allocObject 申请并分配内存 -> init 初始化

对于继承了NSObject的类,如下图:

4.png

其根源还是通过objc_allocWithZone 分配内存空间,通过objc_msgSend的消息机制初始化的.

下面我们看一看swift_allocObject, 都做了些什么。

通过官方资源下载Swift源码, 通过源码HeapObject.cpp分析:

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__) && SWIFT_STDLIB_HAS_DARWIN_LIBMALLOC
    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;
}

注意到malloc相关的内存分配在这里发生了。

回到HeapObject.cpp,注意到返回的对象类型为HeapObject, 所以Swift 对象的内存结构就是HeapObject

跟进HeapObject,在Heap.h 可以看到HeapObject的结构体:

#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

/// The Swift heap-object header.
/// This must match RefCountedStructTy in IRGen.
struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *__ptrauth_objc_isa_pointer metadata;

  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)
  { }
  
  // Initialize a HeapObject header for an immortal object
  constexpr HeapObject(HeapMetadata const *newMetadata,
                       InlineRefCounts::Immortal_t immortal)
  : metadata(newMetadata)
  , refCounts(InlineRefCounts::Immortal)
  { }

#ifndef NDEBUG
  void dump() const SWIFT_USED;
#endif

#endif // __swift__
};

跟进InlineRefCounts refcount, 在 RefCount.h 中发现

typedef RefCounts<InlineRefCountBits> InlineRefCounts;
class RefCounts {
  std::atomic<RefCountBits> refCounts;

  // Out-of-line slow paths.

  SWIFT_NOINLINE
  void incrementSlow(RefCountBits oldbits, uint32_t inc) SWIFT_CC(PreserveMost);

  SWIFT_NOINLINE
  void incrementNonAtomicSlow(RefCountBits oldbits, uint32_t inc);

  SWIFT_NOINLINE
  bool tryIncrementSlow(RefCountBits oldbits);

  SWIFT_NOINLINE
  bool tryIncrementNonAtomicSlow(RefCountBits oldbits);

  SWIFT_NOINLINE
  void incrementUnownedSlow(uint32_t inc);

  public:
  enum Initialized_t { Initialized };
  enum Immortal_t { Immortal };

  // RefCounts must be trivially constructible to avoid ObjC++
  // destruction overhead at runtime. Use RefCounts(Initialized)
  // to produce an initialized instance.
  RefCounts() = default;
  
  // Refcount of a new object is 1.
  constexpr RefCounts(Initialized_t)
    : refCounts(RefCountBits(0, 1)) {}

  // Refcount of an immortal object has top 
  ......
}

// TODO: 推测出InlineRefCounts 本质是两个UInt32的数据结构的组合。

在这里面 metadata 和 refCount` 各占8字节,因此默认Swift对象默认占用16字节大小。

接下来继续跟进HeapMetaData的结构, MetaData.h 中:

/// The common structure of all metadata for heap-allocated types.  A
/// pointer to one of these can be retrieved by loading the 'isa'
/// field of any heap object, whether it was managed by Swift or by
/// Objective-C.  However, when loading from an Objective-C object,
/// this metadata may not have the heap-metadata header, and it may
/// not be the Swift type metadata for the object's dynamic type.
template <typename Runtime>
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
};
using HeapMetadata = TargetHeapMetadata<InProcess>;

在这里发现MetadataKind kindisa 本质应该很相似。

/// Kinds of Swift metadata records.  Some of these are types, some
/// aren't.
enum class MetadataKind : uint32_t {
#define METADATAKIND(name, value) name = value,
#define ABSTRACTMETADATAKIND(name, start, end)                                 \
  name##_Start = start, name##_End = end,
#include "MetadataKind.def"
  
  /// The largest possible non-isa-pointer metadata kind value.
  ///
  /// This is included in the enumeration to prevent against attempts to
  /// exhaustively match metadata kinds. Future Swift runtimes or compilers
  /// may introduce new metadata kinds, so for forward compatibility, the
  /// runtime must tolerate metadata with unknown kinds.
  /// This specific value is not mapped to a valid metadata kind at this time,
  /// however.
  LastEnumerated = 0x7FF,
};

MetadataKind 本质就是一个 uint32_t:

5.png

struct TargetMetadata {
  using StoredPointer = typename Runtime::StoredPointer;

  /// The basic header type.
  typedef TargetTypeMetadataHeader<Runtime> HeaderType;

  constexpr TargetMetadata()
    : Kind(static_cast<StoredPointer>(MetadataKind::Class)) {}
  constexpr TargetMetadata(MetadataKind Kind)
    : Kind(static_cast<StoredPointer>(Kind)) {}

#if SWIFT_OBJC_INTEROP
protected:
  constexpr TargetMetadata(TargetAnyClassMetadata<Runtime> *isa)
    : Kind(reinterpret_cast<StoredPointer>(isa)) {}
#endif

private:
  /// The kind. Only valid for non-class metadata; getKind() must be used to get
  /// the kind value.
  StoredPointer Kind;
  
  ......
    
  getTypeContextDescriptor() const {
    switch (getKind()) {
      case MetadataKind::Class: {
        const auto cls = static_cast<const TargetClassMetadata<Runtime> *>(this);
        if (!cls->isTypeMetadata())
          return nullptr;
        if (cls->isArtificialSubclass())
          return nullptr;
        return cls->getDescription();
      }
      case MetadataKind::Struct:
      case MetadataKind::Enum:
      case MetadataKind::Optional:
        return static_cast<const TargetValueMetadata<Runtime> *>(this)
          ->Description;
      case MetadataKind::ForeignClass:
        return static_cast<const TargetForeignClassMetadata<Runtime> *>(this)
          ->Description;
      default:
        return nullptr;
    }
  }
  
  ......
}

StoredPointer Kind 中保存的就是刚刚的Kind数据。

注意到getKind()Class的分支的cls,由TargetClassMetadata转化而来,

struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
  using StoredPointer = typename Runtime::StoredPointer;
  using StoredSize = typename Runtime::StoredSize;

  TargetClassMetadata() = default;
  constexpr TargetClassMetadata(const TargetAnyClassMetadata<Runtime> &base,
             ClassFlags flags,
             ClassIVarDestroyer *ivarDestroyer,
             StoredPointer size, StoredPointer addressPoint,
             StoredPointer alignMask,
             StoredPointer classSize, StoredPointer classAddressPoint)
    : TargetAnyClassMetadata<Runtime>(base),
      Flags(flags), InstanceAddressPoint(addressPoint),
      InstanceSize(size), InstanceAlignMask(alignMask),
      Reserved(0), ClassSize(classSize), ClassAddressPoint(classAddressPoint),
      Description(nullptr), IVarDestroyer(ivarDestroyer) {}
  
	......
  
}

再跟进TargetAnyClassMetaData

struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
  using StoredPointer = typename Runtime::StoredPointer;
  using StoredSize = typename Runtime::StoredSize;

#if SWIFT_OBJC_INTEROP
  constexpr TargetAnyClassMetadata(TargetAnyClassMetadata<Runtime> *isa,
                                   TargetClassMetadata<Runtime> *superclass)
    : TargetHeapMetadata<Runtime>(isa),
      Superclass(superclass),
      CacheData{nullptr, nullptr},
      Data(SWIFT_CLASS_IS_SWIFT_MASK) {}
#endif

  constexpr TargetAnyClassMetadata(TargetClassMetadata<Runtime> *superclass)
    : TargetHeapMetadata<Runtime>(MetadataKind::Class),
      Superclass(superclass)
#if SWIFT_OBJC_INTEROP
      , CacheData{nullptr, nullptr},
      Data(SWIFT_CLASS_IS_SWIFT_MASK)
#endif
      {}
  ......
}

就可以推测出MetaData的数据结构如下:

struct Metadata {
  var kind: Int
  var superClass: Any.Type
  var cacheData: (Int, Int)
  var data: Int
  var classFlags: Int32
  var instanceAddressPoint: UInt32
  var instanceSize: UInt32
  var instanceAlignmentMask: UInt16
  var reserved: UInt16
  var classSize: UInt32
  var classAddressPoint: UInt32
  var typeDescriptor: UnsafeMutableRawPointer
  var iVarDestroyer: UnsafeRawPointer
}

回到main.swift, 添加如下代码:

struct HeapObject {
    var metaData: UnsafeRawPointer
    var refcount1: UInt32
    var refcount2: UInt32
}

struct Metadata {
  var kind: Int
  var superClass: Any.Type
  var cacheData: (Int, Int)
  var data: Int
  var classFlags: Int32
  var instanceAddressPoint: UInt32
  var instanceSize: UInt32
  var instanceAlignmentMask: UInt16
  var reserved: UInt16
  var classSize: UInt32
  var classAddressPoint: UInt32
  var typeDescriptor: UnsafeMutableRawPointer
  var iVarDestroyer: UnsafeRawPointer
}

class SimpleClass {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let simple = SimpleClass(name: "OK", age: 18)

let rawPtr = Unmanaged.passUnretained(simple as AnyObject).toOpaque()
let ptr = rawPtr.bindMemory(to: HeapObject.self, capacity: 1)
let metadata = ptr.pointee.metaData.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee
print(metadata)
print("end")

可以看到metadata的内存结构经过内存绑定之后如下图:

6.png

可见Metadata 的结构确实如此.