Swift 底层原理初探

Swift 底层原理初探

1. 编译原理

在iOS中我们经常使用Objective-CSwift这两门语言进行编写代码,这两个都是高级语言。我们也都知道高级语言通过一些编译链接后,最终生成机器语言供我们的设备识别,结下来我们就探索一下Swift这门语言是如何编译的。

首先我们来看一段代码,在这段代码中我创建了一个Teacher类,并通过默认的初始化器创建了一个实例对象赋值给了t

class Teacher {
    var age: Int    = 18
    var name:String = "abc"
}

let t = Teacher()

下面我们就来研究一下,这个默认的初始化器到底做了一个什么样的操作?这里我们引入SIL(Swift intermediate language),即Swift中间语言,那么我们先来了解一下什么是SIL

iOS开发中不管是使用Objective-C还是Swift最后都是通过LLVM进行编译的,如下图所示:

16072463328966.jpg

通过上图我们可以看到:

  • Objective-C通过编译器,编译成IR,然后在生成可执行文件.o(也就是机器码)
  • Swift则是通过Swift编译器编译成IR,然后生成生成可执行文件.o

下面我们再来看一下,一个Swift文件的编译过程都经历了那些步骤:

16072473469095.jpg

swift在编译过程中使用的前端编译器是swiftc,和我么之前在oc中使用的clang是有所区别的。我们可以通过如下命令查看swiftc都能做什么事情:

swiftc -h

16072475881317.jpg

还有很多,截图截不全,感兴趣的可以自己执行命令去看看,如果想要详细的对SIL的内容进行了解,可以参考苹果官方的这个视频。(PS:youtobe视频需要科学上网)

2. 通过SIL分析

SIL参考文档

我们新建一个Swift命令行项目,编写如下代码:

16072577574532.jpg
class Teacher {
    var age: Int    = 18
    var name:String = "abc"
}

let t = Teacher()

然后进入到这个目录,执行如下命令:

swiftc -emit-sil main.swift >> ./main.sil && open main.sil

如果打不开,可能是你没有设置用什么软件打开.sil文件,设置一下就好了,我这里用的是VSCode

打开后如下:

16072564082805.jpg

Teacher 类

class Teacher {
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @_hasStorage @_hasInitialValue var name: String { get set }
  @objc deinit
  init()
}

我们可以看到Teacher类中:

  • 有两个存储属性
  • 一个析构方法deinit
  • 一个初始化函数init()

这就是我们Teacher类的在sil文件中的所有信息。

看一下main函数代码

PS:去除代码混淆xcrun swift-demangle s4main1tAA7TeacherCvp

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main1tAA7TeacherCvp            // id: %2
  %3 = global_addr @$s4main1tAA7TeacherCvp : $*Teacher // user: %7
  %4 = metatype $@thick Teacher.Type              // user: %6
  // function_ref Teacher.__allocating_init()
  %5 = function_ref @$s4main7TeacherCACycfC : $@convention(method) (@thick Teacher.Type) -> @owned Teacher // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick Teacher.Type) -> @owned Teacher // user: %7
  store %6 to %3 : $*Teacher                      // 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.swift文件中只有这里贴的那几行代码。但是编译后确有个main函数,说明这里是隐藏了main函数的,下面我们就对生成的.sil文件中的main函数进行分析。

  • @main 这里是标示我们当前main.swift的入口函数,SIL中的标示符以 @作为前缀
  • %0,%1……SIL也叫寄存器,这里我们可以理解为我们日常开发的常量,一旦赋值之后就不可以在修改,如果SIL中还要继续使用,那么就不断的累加数字。同事这里所说的寄存器是虚拟的,最终运行到我们的机器上才会使用真的寄存器
  • alloc_global是创建一个局部变量
  • global_addr是拿到局部变量的地址,赋值给%3
  • metatype是拿到TeacherMetaldata赋值给%4
  • 接下来就是将__allocating_init()的函数地址赋值给%5
  • apply是调用函数,这里是调用%5也就是__allocating_init()%4是参数,并将返回值给%6
  • 然后将%6的值存储到%3,也就是我们刚刚创建的全局变量的地址
  • 然后是构建Intreturn

Teacher.__allocating_init()

main函数中我们调用了Teacher.__allocating_init()函数,那么我们就来看看这个函数。

// Teacher.__allocating_init()
sil hidden [exact_self_class] @$s4main7TeacherCACycfC : $@convention(method) (@thick Teacher.Type) -> @owned Teacher {
// %0 "$metatype"
bb0(%0 : $@thick Teacher.Type):
  %1 = alloc_ref $Teacher                         // user: %3
  // function_ref Teacher.init()
  %2 = function_ref @$s4main7TeacherCACycfc : $@convention(method) (@owned Teacher) -> @owned Teacher // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned Teacher) -> @owned Teacher // user: %4
  return %3 : $Teacher                            // id: %4
} // end sil function '$s4main7TeacherCACycfC'

我们可以看到在__allocating_init函数中:

  • %0存储了metatype(元类,也就是对象的类信息)
  • %1存储来了alloc_ref(对象的引用计数)
  • %2是是Teacherinit函数
  • 通过apply调用%2存储的函数,也就是init,并将返回值存储到%3
  • 返回 %3

其实这里面还有很多方法,比如属性的gettersetter等等,这里放个文件的链接供大家参考:

链接: https://pan.baidu.com/s/1SQCscPJB8lneM0dLtsKF6Q  
密码: ufvb

3. swift 对象&类 探索

在上面我们通过分析sil文件可以知道,对象的初始会调用__allocating_init函数,下面我们就开始探索。

3.1 引入

首先我们就添加个符号断点,看看一下它的汇编调用。


16072614092123.jpg
16072614447821.jpg

添加完符号断点后我们运行代码,运行后可以看到如下汇编结果:

16072615393565.jpg

通过上图我们可以知道:

  • __allocating_init函数内部调用了swift_allocObject
  • 然后还调用了SwiftTest.Teacher.init()

3.2 探索对象的原理

从上面的汇编代码中我们知道在__allocating_init函数内部调用了swift_allocObject函数,下面我们直接来到编译好的Swift源码中搜索swift_allocObject,其实有很多,我们在前面加个_就不多了,因为C++通常会在函数中加个_,如果没加也就只能慢慢找了。

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;
}

源码分析:

  • 首先是断言判断isAlignmentMask,源码如下:
/// Is the given value a valid alignment mask?
static inline bool isAlignmentMask(size_t mask) {
// mask          == xyz01111...
// mask+1        == xyz10000...
// mask&(mask+1) == xyz00000...
// So this is nonzero if and only if there any bits set
// other than an arbitrarily long sequence of low bits.
return (mask & (mask + 1)) == 0;
}

这里是判断内存对齐,如果mask & (mask + 1)true说明对齐,否则就是不对齐,不对齐读出的数据就乱了,CPU读取内存都是按段读的,这是一种空间换时间的概念。

  • 接下来是通过swift_slowAlloc函数去初始化内存,这里是初始化了requiredSize大小的一块内存给对象使用,源码如下:
// When alignMask == ~(size_t(0)), allocation uses the "default"
// _swift_MinAllocationAlignment. This is different than calling swift_slowAlloc
// with `alignMask == _swift_MinAllocationAlignment - 1` because it forces
// the use of AlignedAlloc. This allows manually allocated to memory to always
// be deallocated with AlignedFree without knowledge of its original allocation
// alignment.
//
// For alignMask > (_minAllocationAlignment-1)
// i.e. alignment == 0 || alignment > _minAllocationAlignment:
//   The runtime must use AlignedAlloc, and the standard library must
//   deallocate using an alignment that meets the same condition.
//
// For alignMask <= (_minAllocationAlignment-1)
// i.e. 0 < alignment <= _minAllocationAlignment:
//   The runtime may use either malloc or AlignedAlloc, and the standard library
//   must deallocate using an identical alignment.
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;
}
  • 下面就是通过给对象的metadata赋值初始化一个HeapObject类型的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 *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 LLVM_ATTRIBUTE_USED;
#endif

#endif // __swift__
};
  • 可以看到HeapObject中油两个属性,一个metadata,另一个是个宏定义,在上面的源码也已经列出来了其实就是refCounts
  • 还有两个初始化方法,在这里用的是第一个

所以Swift对象初始的调用流程是__allocating_init->swift_allocObject->_swift_allocObject->_swift_slowAlloc->Malloc

下面我们先来写一段代码,并且在该方法处添加一个断点,看看到底会不会走这个方法,并且初步看看这几个参数。

测试代码:

class Teacher {
  var age:Int = 18
  var name:String = "abc"
}

var t = Teacher()

回车运行后,结果如下图:

16079537076596.jpg

综上所述,对于Swift对象的原理简单的概括如下:

  • 对象初始化后一个HeapObject类型的object
  • 初始化的object有两个属性:
    • metadata:元数据
    • refCount:引用计数
  • 另外我们还可以看到这个对象初始化的内存是40字节
    • metadata占8字节
    • refCount占8字节
    • age属性占8字节
    • name属性占16字节

至于为什么Int是8,String是16,这个我们后续在来介绍,这里就不展开说了,简单打印一下:

print(MemoryLayout<Int>.size)
print(MemoryLayout<String>.size)

print(MemoryLayout<Int>.stride)
print(MemoryLayout<String>.stride)

打印结果:

16079542853182.jpg

关于stride&size的解释:

/// The contiguous memory footprint of `T`, in bytes.
///
/// A type's size does not include any dynamically allocated or out of line
/// storage. In particular, `MemoryLayout<T>.size`, when `T` is a class
/// type, is the same regardless of how many stored properties `T` has.
///
/// When allocating memory for multiple instances of `T` using an unsafe
/// pointer, use a multiple of the type's stride instead of its size.
public static var size: Int { get }

/// The number of bytes from the start of one instance of `T` to the start of
/// the next when stored in contiguous memory or in an `Array<T>`.
///
/// This is the same as the number of bytes moved when an `UnsafePointer<T>`
/// instance is incremented. `T` may have a lower minimal alignment that
/// trades runtime performance for space efficiency. This value is always
/// positive.
public static var stride: Int { get }

3.3 metadata 即 类原理探索

这一节我们就来分析一下上面提到metadata

_swift_allocObject_函数中我们可以看到metadata的类型是HeapMetadata,我们点击跳转过去:

#ifndef __swift__
#include <type_traits>
#include "swift/Basic/type_traits.h"

namespace swift {

struct InProcess;

template <typename Target> struct TargetHeapMetadata;
using HeapMetadata = TargetHeapMetadata<InProcess>;
#else
typedef struct HeapMetadata HeapMetadata;
typedef struct HeapObject HeapObject;
#endif

在这里我们可以看到HeapMetadata是取自TargetHeapMetadata的别名。我们跳转到``中可以看到如下源码:

/// 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
};
  • 在这里我们可以看到TargetHeapMetadata是一个模板的结构体
  • 里面有两个初始化方法
    • 一个是swift初始化的,初始化了一个kind属性
    • 另一个是swiftOC互相操作的时候用的,这里初始化了isa

通过以上我们大概就能想到kind应该跟isa类似。那么在TargetHeapMetadata中我们并没有找到kind属性,那么应该在它的父类中吧,我们点击TargetMetadata,如下:(省略后续代码)

/// The common structure of all type metadata.
template <typename Runtime>
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;
public:
  /// Get the metadata kind.
  MetadataKind getKind() const {
    return getEnumeratedMetadataKind(Kind);
  }
  
  /// Set the metadata kind.
  void setKind(MetadataKind kind) {
    Kind = static_cast<StoredPointer>(kind);
  }
  `
  `
  `
}

我们可以看到在TargetMetadata中有一个私有的StoredPointer类型的属性kind,并且提供了kindgetset函数。StoredPointer类型其实就是unsigned long,这个代码在上面提到的HeapMetadata取别名的InProcess中。

using StoredPointer = typename Runtime::StoredPointer;

using StoredPointer = uintptr_t;

typedef unsigned long           uintptr_t;

找了一圈,那么这个kind到底有什么用呢?

我们点击get方法中的返回值类型``来到如下代码处:

/// 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.def文件,我们点击进去看看,文件中的代码如下:

//===--- MetadataKind.def ---------------------------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This is a file that enables metaprogramming with metadata kinds.
//
//===----------------------------------------------------------------------===//

/// METADATAKIND(Name, Value)
///   Represents a swift native runtime metadata kind. Name is the Name of the
///   metadata kind and Value is the integral value used to identify the value.
#ifndef METADATAKIND
#define METADATAKIND(Name, Value)
#endif

/// ABSTRACTMETADATAKIND(Name, Start, End)
///   Represents an abstraction categorization of a range of metadata kind
///   values. Name is the identifier of the range and Start, End are the
///   beginning and end of the range.
#ifndef ABSTRACTMETADATAKIND
#define ABSTRACTMETADATAKIND(Name, Start, End)
#endif

/// NOMINALTYPEMETADATAKIND(Name, Value)
///   Represents the native metadata kind for a swift nominal type. Name is the
///   name of the kind and Value is the integral value used to identify the
///   value. Delegates to METADATAKIND if not defined.
#ifndef NOMINALTYPEMETADATAKIND
#define NOMINALTYPEMETADATAKIND(Name, Value) METADATAKIND(Name, Value)
#endif

/// A class type.
NOMINALTYPEMETADATAKIND(Class, 0)

/// A struct type.
NOMINALTYPEMETADATAKIND(Struct, 0 | MetadataKindIsNonHeap)

/// An enum type.
/// If we add reference enums, that needs to go here.
NOMINALTYPEMETADATAKIND(Enum, 1 | MetadataKindIsNonHeap)

/// An optional type.
NOMINALTYPEMETADATAKIND(Optional, 2 | MetadataKindIsNonHeap)

/// A foreign class, such as a Core Foundation class.
METADATAKIND(ForeignClass, 3 | MetadataKindIsNonHeap)

/// A type whose value is not exposed in the metadata system.
METADATAKIND(Opaque, 0 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A tuple.
METADATAKIND(Tuple, 1 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A monomorphic function.
METADATAKIND(Function, 2 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// An existential type.
METADATAKIND(Existential, 3 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A metatype.
METADATAKIND(Metatype, 4 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// An ObjC class wrapper.
METADATAKIND(ObjCClassWrapper, 5 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// An existential metatype.
METADATAKIND(ExistentialMetatype, 6 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A heap-allocated local variable using statically-generated metadata.
METADATAKIND(HeapLocalVariable, 0 | MetadataKindIsNonType)

/// A heap-allocated local variable using runtime-instantiated metadata.
METADATAKIND(HeapGenericLocalVariable,
             0 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)

/// A native error object.
METADATAKIND(ErrorObject,
             1 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)

// getEnumeratedMetadataKind assumes that all the enumerated values here
// will be <= LastEnumeratedMetadataKind.

#undef ABSTRACTMETADATAKIND
#undef NOMINALTYPEMETADATAKIND
#undef METADATAKIND
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

所以说这个kind是指的我要具体初始化哪一类的元数据。下面我们就Class类型进行详细分析:

那么我们的metadata只有这一个kind吗?通过对oc的对比,应该不是这个样子的,我们继续从TargetMetadata这个结构体中去寻找,我们找打如下函数:

 /// Get the class object for this type if it has one, or return null if the
  /// type is not a class (or not a class with a class object).
  const TargetClassMetadata<Runtime> *getClassObject() const;

点击跳转找到其真正实现如下:

  template<> inline const ClassMetadata *
  Metadata::getClassObject() const {
    switch (getKind()) {
    case MetadataKind::Class: {
      // Native Swift class metadata is also the class object.
      return static_cast<const ClassMetadata *>(this);
    }
    case MetadataKind::ObjCClassWrapper: {
      // Objective-C class objects are referenced by their Swift metadata wrapper.
      auto wrapper = static_cast<const ObjCClassWrapperMetadata *>(this);
      return wrapper->Class;
    }
    // Other kinds of types don't have class objects.
    default:
      return nullptr;
    }
  }

我们可以看到如果kindClass类型,就直接对this(当前指针,即metadata)强转为ClassMetadataClassMetadata是个别名,其实际是:

using ClassMetadata = TargetClassMetadata<InProcess>;

在这个getClassObject函数中,其返回值是TargetClassMetadata类型,对于Class类型也被强转成了TargetClassMetadata,所以我们点击跳转到TargetClassMetadata中:(省略很多代码)

/// 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;
  `
  `
  `
}

在这类中我们就发现了很多属性(见上面的源码)。我们还能看见TargetClassMetadata也是个模板类型的结构体,继承自TargetAnyClassMetadata,部分代码如下:

/// The portion of a class metadata object that is compatible with
/// all classes, even non-Swift ones.
template <typename Runtime>
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),
      CacheData{nullptr, nullptr},
      Data(SWIFT_CLASS_IS_SWIFT_MASK) {}

#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 does not have a metadata header.

  /// The metadata for the superclass.  This is null for the root class.
  ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass;

  // TODO: remove the CacheData and Data fields in non-ObjC-interop builds.

  /// 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;
`
`
`
}

我们发现TargetAnyClassMetadata也是个模板结构体,继承自TargetHeapMetadata,而TargetHeapMetadata在上面我们就看到了,它继承自TargetMetadata,这回基本就通了,集成关系如下:

TargetClassMetadata : TargetAnyClassMetadata : TargetHeapMetadata : TargetMetadata这其中:

  • TargetMetadata中有1个属性:
    • kind:那种元类型
  • TargetHeapMetadata中没有属性
  • TargetAnyClassMetadata中油3个属性:
    • Superclass:执行父类的指针
    • CacheData:缓存数据,缓存一些动态查找,用于OC运行时
    • Data:元数据头
  • TargetClassMetadata中有8个
    • Flags:类标志
    • InstanceAddressPoint:实例地址指针
    • InstanceSize:该类型实例的所需大小
    • InstanceAlignMask:此类型实例地址的对齐掩码
    • Reserved:保留字段
    • ClassSize:类对象总大小
    • ClassAddressPoint:类对象的偏移量
    • Description:类描述

所以Swift类(Class)的实际组成是:

struct swift_class_t: NSObject{
    void *kind; //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;
// ...
};

3.4 refCount

看到这个属性很自然的就会想到ARC自动引用计数。Swift同样使用ARC

refCount类型是InlineRefCounts

typedef RefCounts<InlineRefCountBits> InlineRefCounts;

可以看到InlineRefCountsRefCounts的别名,RefCounts是一个类,代码很长很长

在这里就先介绍到这里,后面的篇章会详细的介绍Swift中的内存管理。

3.5 小结

  1. Swift中:
    1. 对象的本质是一个HeapObject结构体,默认有两个属性(metadata占8字节、refCount占8字节),总体占用16字节
    2. 类信息存储在metadata元数据中
    3. 使用ARC自动引用计数管理,对象的引用
  2. Objective-C中:
    1. 对象的本质是一个objc_object的结构体,默认有一个isa指针,占8字节
    2. 类信息存储在元类中
    3. 使用ARC自动引用计数管理,对象的引用

4. Swift属性

Swift中属性主要分为存储属性计算属性延迟存储属性类型属性,下面我们逐步分析。

1.1 存储属性

存储属性就是我们最常用的一种方式,这里也分两种:

  1. 常量存储属性,使用let修饰
  2. 变量存储属性,使用var修饰

下面我们通过SIL来看看存储属性

Swift 代码:

class Teacher {
    let age: Int    = 18
    var name:String = "abc"
}

let t = Teacher()

SIL 代码:

class Teacher {
  @_hasStorage @_hasInitialValue final let age: Int { get }
  @_hasStorage @_hasInitialValue var name: String { get set }
  @objc deinit
  init()
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345