Swift is an efficient and secure language. Through the comparison of Swift class and structure, a deep understanding of these two types, good use of class and structure, write efficient and safe code.

For the selection of classes and structures in the development process, Apple has provided suggestions for referenceChoosing Between Structures and Classes
  • Structs are used by default
  • Use classes when you need Objective-C interaction
  • Use classes when instances need to be shared (e.g., singletons, attributes modified by object reference identifier are shared)
  • Use struct compliance protocol to share methods implemented by the protocol

1. The difference between Swift class and structure

class struct note
type Reference types Value types Reference types have the property of sharing instances; Value types areassign to copyAssignment copy feature
Support inheritance YES NO
Support for OC calls YES (need to inherit NSObject) NO
Create a speed slow fast Struct creation is about 1/3 faster than class creationRefer to the Demo
Memory management Reference Type (ARC) Value type (assignment copy)
Memory allocation location Heap (Heap) The Stack (Stack) Because stack access is faster than heap access, value types can be created and accessed faster than reference types
Multithreading safety Access to the NO heap is not multithreaded safe YES stack access is multithreaded safe String, Array, and Dictionary are structs where the data is stored in a class propertycopy-on-write, so it’s not thread read-write safe
Function distribution mode Table dispatch/static dispatch/message dispatch Static distributed
Memory footprint big small Class inherits SwiftObject by default. Instances contain metadata, refCounts, etc. Structs are simply data structures

2. Similarities between Swift class and structure

  • The compiler for OC is LLVM clang.
  • Struct compiler generates an initializer by default, class needs to be written manually.
  • Support for defining properties and methods
  • The extension method is supported
  • Support protocol compliance

3. Class initialization process

Here’s a look at the initialization process for classes that take longer to create and take up more space than structures.

3.1 Classes that inherit NSObject

class Student: NSObject {
    let id: Int
    let name: String
    init(id: Int, name: String) {
        self.id = id
        self.name = name
        super.init()
    }
}
let student = Student(id: 20, name: "Lili")
Copy the code

__let student = student (id: 20, name: “Lili”)__ and then look at assembly Xcode – > Debug – > Debug WorkFlow – > Always Show Disassembly

It can be seen from the breakpoint trace above that the class initialization flow for inheriting NSObject is student.__allocating_init –> objc_allocWithZone –>objc_msgSendSuper2, __objc_allocWithZone__ is familiar, and then OC’s object initialization process is entered.

3.1 pure Swift class

class Student {
    let id: Int
    let name: String
    init(id: Int, name: String) {
        self.id = id
        self.name = name
    }
}
let student = Student(id: 20, name: "Lili")
Copy the code

__let student = student (id: 20, name: “Lili”)__ and then look at assembly Xcode – > Debug – > Debug WorkFlow – > Always Show Disassembly

From the breakpoint trace above, it can be concluded that the pure Swift class initialization process is student.__allocating_init –> swift_allocObject –> swift_slowAlloc –> malloc_zone_malloc

3.2 pureSwift class sourceCode reading

  • It is found in the heapObject.cpp fileswift_allocObject“, swift_allocObject will be calledswift_slowAllocFunction to generateHeapObjectobject
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);
  return object;
}
Copy the code
  • Let’s take a look atswift_allocObjectWhat did you do, a simple callmalloc_zone_mallocThe heapObject.cpp function opens up memory
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; }Copy the code

The result of the source guide is the same as the result of assembly Debug

4. Instance structure analysis of class

4.1 HeapObject

Check Swift source code in the above chapter, initialization process, returned a heapObject type, source analysis, Swift class instance memory structure is a heapObject, heapObject has two attributes, one is metadata, one is refCounts. See HeapObject. H file

struct HeapObject { HeapMetadata const * metadata; InlineRefCounts refCountsCopy the code
  • InlineRefCountsDefined in the refCount.h file,
typedef struct { __swift_uintptr_t refCounts; // make up 8 bytes, typedef uintptr_t __swift_uintptr_t; } InlineRefCountsPlaceholder; typedef InlineRefCountsPlaceholder InlineRefCounts;Copy the code
  • HeapMetadata is defined in the metadata. h file,HeapMetadataInheritance in TargetMetadata
using HeapMetadata = TargetHeapMetadata<InProcess>; Struct TargetHeapMetadata: struct TargetHeapMetadata: TargetMetadata<Runtime>Copy the code
  • TargetHeapMetadata is defined in the metadata. h file and is passedMetadataKind Initialize
/// 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) {} // Swift via 'MetadataKind' initialization function #if SWIFT_OBJC_INTEROP CONSTexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa) : TargetMetadata<Runtime>(isa) {} // Inherit NSObject object by isa pointer initialization function #endif};Copy the code
  • MetadataKind MetadataValues. H is an enum class type that imports metadatakind.def.MetadataKind.defThe file defines allMetadataKind
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" // 'metadatakind. def' file defines all 'MetadataKind' // 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, };Copy the code
  • finishingMetadataKindDefinition, resulting in the following table, which can be inferred, callTargetHeapMetadata(MetadataKind kind)Delta function, ifkind == MetadataKind::ClassStatement initializes a class;
  • TargetHeapMetadata(MetadataKind kind)Function passing in different kind types results in different Meta data structures
name value
name value
Class 0x0
Struct 0x200
Enum 0x201
Optional 0x202
ForeignClass 0x203
Opaque 0x300
Tuple 0x301
Function 0x302
Existential 0x303
Metatype 0x304
ObjCClassWrapper 0x305
ExistentialMetatype 0x306
HeapLocalVariable 0x400
HeapGenericLocalVariable 0x500
ErrorObject 0x501
LastEnumerated 0x7FF
  • TargetHeapMetadata inherits TargetMetadata, finds this function in TargetMetadata, and gets when kind == MetadataKind::ClassTargetClassMetadatatype
  ConstTargetMetadataPointer<Runtime, TargetTypeContextDescriptor>
  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;
    }
  }
Copy the code
  • TargetClassMetadata Is the base class of all classes, TargetClassMetadata inheritanceTargetAnyClassMetadata
/// The structure of all class metadata.  This structure is embedded
/// directly within the class's heap metadata structure and therefore
/// cannot be extended without an ABI break.
///
/// Note that the layout of this type is compatible with the layout of
/// an Objective-C class.
template <typename Runtime>
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;

public:
  /// A function for destroying instance variables, used to clean up after an
  /// early return from a constructor. If null, no clean up will be performed
  /// and all ivars must be trivial.
  TargetSignedPointer<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;

  // After this come the class members, laid out as follows:
  //   - class members for the superclass (recursively)
  //   - metadata reference for the parent, if applicable
  //   - generic parameters for this class
  //   - class variables (if we choose to support these)
  //   - "tabulated" virtual methods

  using TargetAnyClassMetadata<Runtime>::isTypeMetadata;

  ConstTargetMetadataPointer<Runtime, TargetClassDescriptor>
  getDescription() const {
    assert(isTypeMetadata());
    return Description;
  }

  typename Runtime::StoredSignedPointer
  getDescriptionAsSignedPointer() const {
    assert(isTypeMetadata());
    return Description;
  }

  void setDescription(const TargetClassDescriptor<Runtime> *description) {
    Description = description;
  }

  // [NOTE: Dynamic-subclass-KVO]
  //
  // Using Objective-C runtime, KVO can modify object behavior without needing
  // to modify the object's code. This is done by dynamically creating an
  // artificial subclass of the the object's type.
  //
  // The isa pointer of the observed object is swapped out to point to
  // the artificial subclass, which has the following properties:
  // - Setters for observed keys are overridden to additionally post
  // notifications.
  // - The `-class` method is overridden to return the original class type
  // instead of the artificial subclass type.
  //
  // For more details, see:
  // https://www.mikeash.com/pyblog/friday-qa-2009-01-23.html

  /// Is this class an artificial subclass, such as one dynamically
  /// created for various dynamic purposes like KVO?
  /// See [NOTE: Dynamic-subclass-KVO]
  bool isArtificialSubclass() const {
    assert(isTypeMetadata());
    return Description == nullptr;
  }
  void setArtificialSubclass() {
    assert(isTypeMetadata());
    Description = nullptr;
  }

  ClassFlags getFlags() const {
    assert(isTypeMetadata());
    return Flags;
  }
  void setFlags(ClassFlags flags) {
    assert(isTypeMetadata());
    Flags = flags;
  }

  StoredSize getInstanceSize() const {
    assert(isTypeMetadata());
    return InstanceSize;
  }
  void setInstanceSize(StoredSize size) {
    assert(isTypeMetadata());
    InstanceSize = size;
  }

  StoredPointer getInstanceAddressPoint() const {
    assert(isTypeMetadata());
    return InstanceAddressPoint;
  }
  void setInstanceAddressPoint(StoredSize size) {
    assert(isTypeMetadata());
    InstanceAddressPoint = size;
  }

  StoredPointer getInstanceAlignMask() const {
    assert(isTypeMetadata());
    return InstanceAlignMask;
  }
  void setInstanceAlignMask(StoredSize mask) {
    assert(isTypeMetadata());
    InstanceAlignMask = mask;
  }

  StoredPointer getClassSize() const {
    assert(isTypeMetadata());
    return ClassSize;
  }
  void setClassSize(StoredSize size) {
    assert(isTypeMetadata());
    ClassSize = size;
  }

  StoredPointer getClassAddressPoint() const {
    assert(isTypeMetadata());
    return ClassAddressPoint;
  }
  void setClassAddressPoint(StoredSize offset) {
    assert(isTypeMetadata());
    ClassAddressPoint = offset;
  }

  uint16_t getRuntimeReservedData() const {
    assert(isTypeMetadata());
    return Reserved;
  }
  void setRuntimeReservedData(uint16_t data) {
    assert(isTypeMetadata());
    Reserved = data;
  }

  /// Get a pointer to the field offset vector, if present, or null.
  const StoredPointer *getFieldOffsets() const {
    assert(isTypeMetadata());
    auto offset = getDescription()->getFieldOffsetVectorOffset();
    if (offset == 0)
      return nullptr;
    auto asWords = reinterpret_cast<const void * const*>(this);
    return reinterpret_cast<const StoredPointer *>(asWords + offset);
  }

  uint32_t getSizeInWords() const {
    assert(isTypeMetadata());
    uint32_t size = getClassSize() - getClassAddressPoint();
    assert(size % sizeof(StoredPointer) == 0);
    return size / sizeof(StoredPointer);
  }

  /// Given that this class is serving as the superclass of a Swift class,
  /// return its bounds as metadata.
  ///
  /// Note that the ImmediateMembersOffset member will not be meaningful.
  TargetClassMetadataBounds<Runtime>
  getClassBoundsAsSwiftSuperclass() const {
    using Bounds = TargetClassMetadataBounds<Runtime>;

    auto rootBounds = Bounds::forSwiftRootClass();

    // If the class is not type metadata, just use the root-class bounds.
    if (!isTypeMetadata())
      return rootBounds;

    // Otherwise, pull out the bounds from the metadata.
    auto bounds = Bounds::forAddressPointAndSize(getClassAddressPoint(),
                                                 getClassSize());

    // Round the bounds up to the required dimensions.
    if (bounds.NegativeSizeInWords < rootBounds.NegativeSizeInWords)
      bounds.NegativeSizeInWords = rootBounds.NegativeSizeInWords;
    if (bounds.PositiveSizeInWords < rootBounds.PositiveSizeInWords)
      bounds.PositiveSizeInWords = rootBounds.PositiveSizeInWords;

    return bounds;
  }

#if SWIFT_OBJC_INTEROP
  /// Given a statically-emitted metadata template, this sets the correct
  /// "is Swift" bit for the current runtime. Depending on the deployment
  /// target a binary was compiled for, statically emitted metadata templates
  /// may have a different bit set from the one that this runtime canonically
  /// considers the "is Swift" bit.
  void setAsTypeMetadata() {
    // If the wrong "is Swift" bit is set, set the correct one.
    //
    // Note that the only time we should see the "new" bit set while
    // expecting the "old" one is when running a binary built for a
    // new OS on an old OS, which is not supported, however we do
    // have tests that exercise this scenario.
    auto otherSwiftBit = (3ULL - SWIFT_CLASS_IS_SWIFT_MASK);
    assert(otherSwiftBit == 1ULL || otherSwiftBit == 2ULL);

    if ((this->Data & 3) == otherSwiftBit) {
      this->Data ^= 3;
    }

    // Otherwise there should be nothing to do, since only the old "is
    // Swift" bit is used for backward-deployed runtimes.
    
    assert(isTypeMetadata());
  }
#endif

  bool isStaticallySpecializedGenericMetadata() const {
    auto *description = getDescription();
    if (!description->isGeneric())
      return false;

    return this->Flags & ClassFlags::IsStaticSpecialization;
  }

  bool isCanonicalStaticallySpecializedGenericMetadata() const {
    auto *description = getDescription();
    if (!description->isGeneric())
      return false;

    return this->Flags & ClassFlags::IsCanonicalStaticSpecialization;
  }

  static bool classof(const TargetMetadata<Runtime> *metadata) {
    return metadata->getKind() == MetadataKind::Class;
  }
}
Copy the code
  • By collatingTargetClassMetadata All attributes of, resulting in the following data structure
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
}
Copy the code

4.2 Restore Metadata data structure by Swift code

  • Through the analysis and summary in section 4.1, HeapObject is described through Swift data structure, and thenbindMemoryPerform memory data mapping to verify their assumptions
struct HeapObject{ var metadata: UnsafeRawPointer var refCounts: UInt64 } 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} // Define a class class Student {let id = 10 let name = "Lili"} let Student = Student() // Let pRawHeapObject = Unmanaged. PassUnretained (student).toopaque () // Will ` pRawHeapObject ` pointing to the content of the map to ` HeapObject ` data structure let HeapObject = pRawHeapObject. BindMemory (to: HeapObject. Self, capacity: 1). Pointee print(heapObject) // Map 'heapObject.metadata' to 'metadata' let metadata = heapObject.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee print(metadata) print("Student instance size is:", class_getInstanceSize(Student.self))Copy the code

In the debugging window, view the result: superClass: _TtCs12_SwiftObject, instanceSize: 40 Meets the expectations

HeapObject(metadata: 0x00000001000081a8, refCounts: 3)
Metadata(kind: 4295000432, superClass: _TtCs12_SwiftObject, cacheData: (7402896512, 140943646785536), data: 4302401618, classFlags: 2, instanceAddressPoint: 0, instanceSize: 40, instanceAlignmentMask: 7, reserved: 0, classSize: 120, classAddressPoint: 16, typeDescriptor: 0x0000000100003cf0, iVarDestroyer: 0x0000000000000000)
Student instance size is: 40
Copy the code

5 concludes

  • The Swift class finally passedmalloc_zone_malloc Allocated in heap space, heap space allocation efficiency is lower than stack, read and write heap space is not thread safe.
  • inheritanceNSObjectClass, by calling TargetHeapMetadata(TargetAnyClassMetadata * ISA) function, isa pointer as a parameter, initialize a TargetHeapMetadata structure, and pure Swift class data structure basically consistent, So it can be called by OC.
  • HeapObjectandMetadataDescription takes up a lot of memory space, so the class takes up more space than the structure.
  • Heapobjec. refCounts Locking is thread safe, but frequent reads and writes consume resources, which is one of the reasons why the analogy structure is slow.

Reference Documents:

  • Copy – on – write: github.com/apple/swift…
  • Swift when writing copy (copy – on – write) : www.jianshu.com/p/e8b1336d9…
  • Choosing Between Structures and Classes: developer.apple.com/documentati…