swift01:类和结构体

318 阅读6分钟

swift的struct和class的异同?

相同点

·定义属性

·定义方法

·定义初始化器

·定义下标以及使用下标语法访问提供对其值访问

·可以通过extension来拓展 ·遵循协议protocol来提供某种功能

最本质的区别:

·struct是值类型,分配内存在栈区。 ·class是引用类型,分配内存在堆区。

验证:

结构体赋值是值拷贝,栈上不同内存。

类赋值是指针拷贝,指向堆上同一个内存地址。

struct和class的其他不同:

·类有继承特性,struct没有 ·class可以类型转换(能够在运行时检查和解释类实例的类型) ·类有析构函数用来释放其分配的资源(堆内存) ·引用计数允许对一个实例多个引用(引用类型)

struct和class执行效率上的差别

·struct在栈上,线程安全 ·struct不需要分配、销毁堆空间,访问效率更高。

image.png

小结:

struct作为值类型相对于class有更加安全高效的访问。

在不需要的继承的合适场景,可以考虑多使用struct。

struct和class性能差别的官方案例

类的初始化器

默认初始化器

编译器不会为class提供默认的初始化器,需要程序员自己提供一个指定初始化器。

struct构体会提供默认的初始化方法。

如果calss的所有属性都有默认的初始化值,或者可空,也可以不提供指定初始化器

指定初始化器

每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器,类偏向于少量指定初始化器,一个类通常只有一个指定初始化器。

class QPPerson{
    var age: Int
    var name: String
    //指定初始化器
    init(age: Int,name: String) {
        self.age = age;
        self.name = name;
    }
}

·指定初始化器必须保证向上委托给父类初始化器之前,本类引入的所有属性都要初始化完成。

·指定初始化器必须先向上委托父类初始化器,然后才能为其继承来的属性设置新值。

便捷初始化器

便捷初始化器必须先委托同类中的其他初始化器,然后再为任意属性赋新值。

截屏2021-12-31 下午4.56.55

可失败初始化器

当前因为参数的不合法或者外部条件的不满足,存在初始化失败的情况。

    init?(age: Int,name: String) {
        if age < 18 {
            print("未成nian人");
            return nil
        }
        self.age = age;
        self.name = name;
    }

必要初始化器

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

    required init(age: Int,name: String) {
        if age < 18 {
            print("未成nian人");
            return nil
        }
        self.age = age;
        self.name = name;
    }

对象和类的底层结构探索

swift的编译

iOS开发语言不管是OC还是Swift后端都是通过LLVM进行编译的,如下图所示:

基于LLVM的编译前后端分离式架构。中间通过LLVM IR承接。

oc的编译前端是clang。

swift的编译前端是swift。

swift的编译过程,如下图:

截屏2021-12-31 下午5.31.20

//相关的编译指令

// 分析输出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文件分析

sil官方文档

swift编译比clang更加复杂,多了一种新的中间语言SIL .

SIL(swift intermediate language)特点:

1.能够完整的表达程序的语义。 2.被设计用于代码生成和静态分析 3.处于编译的流水线中,而不是独立于之外 4.在源代码和 LLVM 之间架起了抽象的桥梁

生成.sil文件

示例代码

class QPIOSCoder{
    var name:String = "ramon"
    var level:Int = 0
}
var coder = QPIOSCoder()

设置xcode的build的run自动生成.sil文件

截屏2021-12-31 下午5.44.43

解析.sil文件

截屏2021-12-31 下午6.15.00

swift的类型安全
        int8_t x = 100;
        int8_t y = x + 100;
        NSLog(@"%d",y);

编译器,可以检测出OC检测不出的溢出。 截屏2021-12-26 上午12.42.43.png

swift对象和类结构探索

继承自NSObject的swift对象的创建截屏2022-01-02 下午10.00.34

可以很明显看到,继承自NSObject,对象创建就调用objc_allocWithZone函数及objc_msgSend函数。

如果在swift开发中想使用OC的runtime特性,可以使用这种方式。

纯swift对象的创建

__allocating_init->swift_allocObject->

为了追踪纯swift对象的创建及底层数据结构,我们需要看下swift源码

源码分析

HeapObject *swift::swift_allocObject(HeapMetadata const *metadata,
                                     size_t requiredSize,
                                     size_t requiredAlignmentMask) {
  CALL_IMPL(swift_allocObject, (metadata, requiredSize, requiredAlignmentMask));
}

看下CALL_IMPL的定义

#define CALL_IMPL(name, args) do { \
    void *fptr; \
    memcpy(&fptr, (void *)&_ ## name, sizeof(fptr)); \
    extern char _ ## name ## _as_char asm("__" #name "_"); \
    fptr = __ptrauth_swift_runtime_function_entry_strip(fptr); \
    if (SWIFT_UNLIKELY(fptr != &_ ## name ## _as_char)) \
      return _ ## name args; \
    return _ ## name ## _ args; \
} while(0)

对函数名做了拼接,真正调用的应该是_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;
}

可以看到底层的数据类型是HeapObject。

// The members of the HeapObject header that are not shared by a
// standard Objective-C instance
#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;
  //refcounts见名知意,类型经过多次typedef转换
  //可以简单看成是UInt64:64位系统下。
  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__
};

结论1:
//简化后的swift对象的数据结构
struct HeapObject{
  HeapMetadata const *__ptrauth_objc_isa_pointer metadata;
  UInt64 refCounts;
}

继续追踪源码查看HeapMetaData *,看注释可以看做是我们OC的isa指针。

//定义别名
using HeapMetadata = TargetHeapMetadata<InProcess>;
//继承自TargetMetadata
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
};
//TargetMetadata源码太长,做了精简
struct TargetMetadata {
  //初始化函数
private:
  //唯一的成员kind,标识类型枚举
  StoredPointer Kind;
  //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;
    }
  }

kind所有类型

截屏2022-01-02 下午11.05.40

可以认为TargetMetadata是所有swift数据类型的基类。

另外我们也注意到了TargetClassMetadata,它是不是我们swift中class的底层数据结构呢?它的数据成员有哪些呢?

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) {}

  // The remaining fields are valid only when isTypeMetadata().
  // The Objective-C runtime knows the offsets to some of these fields.
  // Be careful when accessing them.

  /// Swift-specific class flags.
  ClassFlags Flags;

  /// The address point of instances of this type.
  uint32_t InstanceAddressPoint;

  /// The required size of instances of this type.
  /// 'InstanceAddressPoint' bytes go before the address point;
  /// 'InstanceSize - InstanceAddressPoint' bytes go after it.
  uint32_t InstanceSize;

  /// The alignment mask of the address point of instances of this type.
  uint16_t InstanceAlignMask;

  /// Reserved for runtime use.
  uint16_t Reserved;

  /// The total size of the class object, including prefix and suffix
  /// extents.
  uint32_t ClassSize;

  /// The offset of the address point within the class object.
  uint32_t ClassAddressPoint;

  // Description is by far the most likely field for a client to try
  // to access directly, so we force access to go through accessors.
private:
  /// An out-of-line Swift-specific description of the type, or null
  /// if this is an artificial subclass.  We currently provide no
  /// supported mechanism for making a non-artificial subclass
  /// dynamically.
  TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
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
      {}

#if SWIFT_OBJC_INTEROP
  // Allow setting the metadata kind to a class ISA on class metadata.
  using TargetMetadata<Runtime>::getClassISA;
  using TargetMetadata<Runtime>::setClassISA;
#endif

  // Note that ObjC classes do not have a metadata header.

  /// The metadata for the superclass.  This is null for the root class.
  TargetSignedPointer<Runtime, const TargetClassMetadata<Runtime> *
                                   __ptrauth_swift_objc_superclass>
      Superclass;

#if SWIFT_OBJC_INTEROP
  /// The cache data is used for certain dynamic lookups; it is owned
  /// by the runtime and generally needs to interoperate with
  /// Objective-C's use.
  TargetPointer<Runtime, void> CacheData[2];

  /// The data pointer is used for out-of-line metadata and is
  /// generally opaque, except that the compiler sets the low bit in
  /// order to indicate that this is a Swift metatype and therefore
  /// that the type metadata header is present.
  StoredSize Data;
  
  static constexpr StoredPointer offsetToData() {
    return offsetof(TargetAnyClassMetadata, Data);
  }
#endif

  /// Is this object a valid swift type metadata?  That is, can it be
  /// safely downcast to ClassMetadata?
  bool isTypeMetadata() const {
#if SWIFT_OBJC_INTEROP
    return (Data & SWIFT_CLASS_IS_SWIFT_MASK);
#else
    return true;
#endif
  }
  /// A different perspective on the same bit
  bool isPureObjC() const {
    return !isTypeMetadata();
  }
};

推到下:TargetClassMetaData的数据成员,我们直接用struct嵌套来表示继承。

struct TargetClassMetaData{
  struct TargetAnyClassMetaData{
    struct TargetHeapMetaData{
      StoredPointer Kind;
    }
    TargetSignedPointer<Runtime, const TargetClassMetadata<Runtime> *
                                   __ptrauth_swift_objc_superclass>
      Superclass;
    TargetPointer<Runtime, void> CacheData[2];
    StoredSize Data;  
  }
    ClassFlags Flags;

  /// The address point of instances of this type.
  uint32_t InstanceAddressPoint;

  /// The required size of instances of this type.
  /// 'InstanceAddressPoint' bytes go before the address point;
  /// 'InstanceSize - InstanceAddressPoint' bytes go after it.
  uint32_t InstanceSize;

  /// The alignment mask of the address point of instances of this type.
  uint16_t InstanceAlignMask;

  /// Reserved for runtime use.
  uint16_t Reserved;

  /// The total size of the class object, including prefix and suffix
  /// extents.
  uint32_t ClassSize;

  /// The offset of the address point within the class object.
  uint32_t ClassAddressPoint;

  TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
   TargetSignedPointer<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;
}

进一步精简

小结2:
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 
}

验证数据结构

struct HeapObject{
    var metadata: UnsafeRawPointer
    var refcount: UInt64
}
struct MetaData{
    var kind: UnsafeRawPointer
    var superClass: Any.Type
    var cacheData: (Int,Int)
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reverved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDescriptor: UnsafeMutableRawPointer
    var iVarDestroyer: UnsafeRawPointer

}
class QPPerson{
    var age: Int
    var name: String
    init(age:Int,name:String) {
        self.age = 18
        self.name = "qlpdddddddd"
    }
}

var p = QPPerson(age: 18, name: "ramon")
let objcRawPtr = Unmanaged.passUnretained(p as AnyObject).toOpaque()
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
let metaData = objcPtr.pointee.metadata.bindMemory(to: MetaData.self, capacity: MemoryLayout<MetaData>.stride).pointee
print(MemoryLayout.stride(ofValue: "ddddddddddd"))
print(objcPtr.pointee)
print(metaData)
print("end")

打印如下:截屏2022-01-02 下午11.34.04

通过打印结果,可以验证我们推导的swift对象和class的底层数据结构正确😄。