修行

Swift底层探索(一):编译流程

2020-12-05  本文已影响0人  HotPotCat

Swift编译

Swift编译究竟是一个怎样的过程呢?从Swift语言到cpu能够识别的机器码这之间究竟经过了哪些步骤呢?
我们先写一个简单的HotpotCat类如下:

import Foundation

class HotpotCat {
    var name: String = "Hotpot"
    var age: Int = 1
}

var hotpot = HotpotCat()

通过默认的初始化器我们构造了一个实例对象hotpot,类比OC在这个过程中alloc(内存分配) init(初始化)。那么Swift这个默认的初始化器到底做了什么操作?这里我们需要了解SIL(Swift intermediate language),再阅读SIL之前,先了解下什么是SIL。从字面意思理解它是一门中间语言。
iOS开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的,编译过程如下:

image.png

OC通过clang编译器,编译成IR,然后生成可执行文件.o(也就会机器码);
Swift通过Swift编译器编译成IR,然后生成可执行文件。

那么一个Swift文件编译的整个过程中都经历了什么呢?


Swift编译过程

Swift和OC的区别也就是中间SIL生成的这一部分。我们一般阅读经过优化后的SIL文件。

swift在编译的过程中使用的前段编译器是swiftc,和OC中使用的Clang是有区别的。可以通过swiftc -h查看swiftc都能哪些功能:

USAGE: swiftc
MODES:
  -dump-ast              Parse and type-check input file(s) and dump AST(s)
  -dump-parse            Parse input file(s) and dump AST(s)
  -dump-pcm              Dump debugging information about a precompiled Clang module
  -dump-scope-maps <expanded-or-list-of-line:column>
                         Parse and type-check input file(s) and dump the scope map(s)
  -dump-type-info        Output YAML dump of fixed-size types from all imported modules
  -dump-type-refinement-contexts
                         Type-check input file(s) and dump type refinement contexts(s)
  -emit-assembly         Emit assembly file(s) (-S)
  -emit-bc               Emit LLVM BC file(s)
  -emit-executable       Emit a linked executable
  -emit-imported-modules Emit a list of the imported modules
  -emit-ir               Emit LLVM IR file(s)
  -emit-library          Emit a linked library
  -emit-object           Emit object file(s) (-c)
  -emit-pcm              Emit a precompiled Clang module from a module map
  -emit-sibgen           Emit serialized AST + raw SIL file(s)
  -emit-sib              Emit serialized AST + canonical SIL file(s)
  -emit-silgen           Emit raw SIL file(s)
  -emit-sil              Emit canonical SIL file(s)
  -index-file            Produce index data for a source file
  -parse                 Parse input file(s)
  -print-ast             Parse and type-check input file(s) and pretty print AST(s)
  -resolve-imports       Parse and resolve imports in input file(s)
  -typecheck             Parse and type-check input file(s)

简单分析下
-dump-parse
Parse input file(s) and dump AST(s)、分析输出AST

swiftc -dump-parse main.swift >> ./main.parse

-dump-ast
Parse and type-check input file(s) and dump AST(s)。分析并且检查类型输出AST。

swiftc -dump-ast main.swift >> ./main.ast

从以上可以看出这两个都生成了抽象语法树,-dump-ast多了类型检查。我们摘录一段对比就可以看出一个bind Swift String,一个为none。

//-dump-parse
    (pattern_binding_decl range=[main.swift:11:5 - line:11:24]
      (pattern_typed
        (pattern_named 'name')
        (type_ident
          (component id='String' bind=none)))

//-dump-ast
    (pattern_binding_decl range=[main.swift:11:5 - line:11:24]
      (pattern_typed type='String'
        (pattern_named type='String' 'name')
        (type_ident
          (component id='String' bind=Swift.(file).String)))

-emit-silgen
Emit raw SIL file(s),简单理解为生成未加工的SIL文件。

swiftc -emit-silgen main.swift >> ./main.silgen
image.png

-emit-sil
Emit canonical SIL file(s),经过优化处理的SIL文件

swiftc -emit-sil main.swift >> ./main.sil
image.png

对比两者可以看到经过优化的SIL多了引用计数相关的操作,那么猜测引用计数是在SIL优化这一步进行处理的。

-emit-ir
Emit LLVM IR file(s), 生成LLVM的IR中间表示层文件

swiftc -emit-ir main.swift >> ./main.ir

-emit-assembly
Emit assembly file(s) (-S),生成汇编语言

swiftc -emit-assembly  main.swift >> ./main.assembly

-emit-bc
Emit LLVM BC file(s),生成字节码二进制

swiftc -emit-bc  main.swift >> ./main.bc

-o
生成可执行的二进制文件

swiftc -o  main.o  main.swift

这里main.swift文件是一个只依赖了Foundation库的文件,直接使用-emit就可以了,这么直接编译iOS项目中的Swift源文件会报错,因为依赖了UIKit或其它库,需要额外指定-target和-sdk。

  • sdk 直接指定Xcode对应的SDK.
//模拟器
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk`
//真机
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk`
//mac
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk

可以通过命令代替真实路径,当然真实路径也没问题,以下分别对应模拟器、真机、mac 路径:
xcrun --show-sdk-path --sdk iphonesimulator/iphoneos/macosx

  • target (指令集-apple-ios版本/指令集-apple-ios版本-simulator
    这里在操作时用mac/iphone代替apple也是可以的,分别指代maciphone平台。当然TVwatch也同理。
    对应目标平台i386x86_64armv7armv7sarm64arm64e等,这里iOS版本最低iOS7。
    如:arm64e-apple-ios14.0x86_64-apple-ios14-simulatorarmv7-iphone-ios12.0arm64-mac-macosx11.0
//模拟器
swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk iphonesimulator)  -target x86_64-apple-ios14.0-simulator ViewController.swift >> ./ViewController.sil
//真机
swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk iphoneos)  -target arm64e-apple-ios14.0 ViewController.swift >> ./ViewController.sil
//mac
swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk macosx) -target arm64-apple-macosx11.0 ViewController.swift >> ./ViewController.sil

更详细的介绍可以观看官方讲解视频

SIL分析

接着上面的HotpotCat例子,我们直接在终端生成SIL文件并保存为main.sil查看。(当然也可以尝试其它命令-emit-silgen,-dump-ast)。

swiftc -emit-sil main.swift >> ./main.sil
class HotpotCat {
  @_hasStorage @_hasInitialValue var name: String { get set }
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @objc deinit
  init()
}

可以看到HotpotCat有两个存储属性,有一个init方法,有一个objc标识的deinit方法。

main分析

继续看sil文件会找到一个main函数,这个main函数其实就是swift隐藏的main函数,swift不过进行了省略而已。

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main6hotpotAA9HotpotCatCvp     // id: %2
  %3 = global_addr @$s4main6hotpotAA9HotpotCatCvp : $*HotpotCat // user: %7
  %4 = metatype $@thick HotpotCat.Type            // user: %6
  // function_ref HotpotCat.__allocating_init()
  %5 = function_ref @$s4main9HotpotCatCACycfC : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat // user: %7
  store %6 to %3 : $*HotpotCat                    // 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'

在SIL中以@作为标识符名称前缀

 xcrun  swift-demangle s4main6hotpotAA9HotpotCatCvp
$s4main6hotpotAA9HotpotCatCvp ---> main.hotpot : main.HotpotCat

Swift实例对象

__allocating_init()分析

那么s4main9HotpotCatCACycfC(__allocating_init)究竟干了什么呢?

// HotpotCat.__allocating_init()
sil hidden [exact_self_class] @$s4main9HotpotCatCACycfC : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat {
// %0 "$metatype"
bb0(%0 : $@thick HotpotCat.Type):
  %1 = alloc_ref $HotpotCat                       // user: %3
  // function_ref HotpotCat.init()
  %2 = function_ref @$s4main9HotpotCatCACycfc : $@convention(method) (@owned HotpotCat) -> @owned HotpotCat // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned HotpotCat) -> @owned HotpotCat // user: %4
  return %3 : $HotpotCat                          // id: %4
} // end sil function '$s4main9HotpotCatCACycfC'
Allocates an object of reference type T. The object will be initialized with retain count 1;

xcode断点验证

我们可以设置一个符号断点(__allocating_init)验证一下:


image.png
SwiftBasic`HotpotCat.__allocating_init():
->  0x100003be0 <+0>:  push   rbp
    0x100003be1 <+1>:  mov    rbp, rsp
    0x100003be4 <+4>:  push   r13
    0x100003be6 <+6>:  push   rax
    0x100003be7 <+7>:  mov    esi, 0x28
    0x100003bec <+12>: mov    edx, 0x7
    0x100003bf1 <+17>: mov    rdi, r13
    0x100003bf4 <+20>: call   0x100003d5e               ; symbol stub for: swift_allocObject
    0x100003bf9 <+25>: mov    r13, rax
    0x100003bfc <+28>: call   0x100003c40               ; SwiftBasic.HotpotCat.init() -> SwiftBasic.HotpotCat at main.swift:10
    0x100003c01 <+33>: add    rsp, 0x8
    0x100003c05 <+37>: pop    r13
    0x100003c07 <+39>: pop    rbp
    0x100003c08 <+40>: ret    

Swift源码验证

同样可以通过VSCode调试源码,在swift的REPL(命令交互行)


image.png

swift_allocObject

在定义变量hotpot敲回车前,我们给_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;
}
image.png

敲回车后看一下本地变量:大小需要40字节,8字节对齐。

这里对齐的目的是对于64位cpu而言,一次能读取8个字节(连续)。在这个过程当中连续读8个字节最快,对于创建出来的实例对象应该是8的倍数,偶数。8的倍数是为了在整个内存寻址周期的过程当中更加的具有效率,不足8的倍数会补足。目的是以空间换时间来提高访问存储效率。这里掩码为7代表的就是8字节对齐。

image.png

swift_slowAlloc

接着我们看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;
}

p这里可以看到在堆中创建size大小的空间。这个size存储我们当前的实例变量

#if defined(__APPLE__)
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size);

swift_allocObject函数的返回值是HeapObject,意味着我们创建出来的实例变量在内存当中是HeapObject。

  new (object) HeapObject(metadata);

创建HeapObject的参数是metadata,也就是通过元数据初始化HeapObject这个结构体。

HeapObject

HeapObject内容如下(c++代码):

#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

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

可以看到这函数需要metadata(元数据)和refCounts(引用计数)。可以看到metadata是一个指针类型(8字节),refCounts可以看到是一个InlineRefCounts。

InlineRefCounts、RefCounts

InlineRefCounts定义如下,RefCounts点进去可以看到是一个class。

typedef RefCounts<InlineRefCountBits> InlineRefCounts;

class RefCounts {
  std::atomic<RefCountBits> refCounts;

  // Out-of-line slow paths.

所以refCounts也是一个指针类型(8字节)。上面我们在VSCode调试的时候看到hotpot对象requiredSize占40字节,那么这里其实Int占8字节,String占16字节。
我们也可以通过代码直接验证

print(MemoryLayout<String>.size)
print(MemoryLayout<Int>.stride)
print(class_getInstanceSize(HotpotCat.self))
16
8
40

那么对我们的hotpot实例对象本质是一个HeapObject结构体(默认16字节大小)。相比于OC实例对象本质是结构体objc_object,他有一个isa指针默认8字节。swift相比oc多了一个refCounted。

类对象

类结构是HeapMetadata结构,OC中类结构是objc_class。

HeapMetadata

using HeapMetadata = TargetHeapMetadata<InProcess>;

从源码中可以看到HeapMetadata是一个别名,实际是TargetHeapMetadata。

TargetHeapMetadata

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

TargetHeapMetadata是一个模板类型,接收一个参数InProcess, InProcess定义了一些需要的数据结构。看TargetHeapMetadata源码发现其中并没有属性,只有初始化方法,初始化方法是MetadataKind kind,那么我们看看他的父结构体TargetMetadata。

TargetMetadata

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

可以看到有一个StoredPointer Kind

  using StoredPointer = typename Runtime::StoredPointer;

再后头看下InProcess,可以找到

  using StoredPointer = uintptr_t;

继续看uintptr_t,可以看到是unsigned long类型,那么kind就是unsigned long类型。kind其实是来区分是哪种类型的元数据。

typedef unsigned long           uintptr_t;

TargetHeapMetadata中点开MetadataKind可以看到一个MetadataKind.def文件,点开.

MetadataKind.def

/// 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)

这里面记录了我们当前所有元数据类型。
kind种类:

name value
Class 0x0
Struct 0x200
Enum 0x201
Optional 0x202
ForeignClass 0x202
Opaque 0x300
Tuple 0x301
Function 0x302
Existential 0x303
Metatype 0x304
ObjCClassWrapper 0x305
ExistentialMetatype 0x306
HeapLocalVariable 0x400
HeapGenericLocalVariable 0x500
ErrorObject 0x501
LastEnumerated 0x7FF

回到TargetMetadata,只有一个kind属性,在这个文件中我们往下找看看,706行可以看到有一个getClassObject方法,返回类型为TargetClassMetadata

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

  void *allocateMetadata(size_t size, size_t align);

本身是匹配kind返回对应的值,就是上表对应kind的值。如果是class那么把this也就是当前指针强转为ClassMetadata.
通过lldb po metadata 验证为class

Stop reason: exec
po metadata->getKind()
Class
po metadata->getClassObject()
0x0000000110effc70
x/8g 0x0000000110effc70
//这里就是元数据里面记录的数据了
0x110effc70: 0x0000000110effc38 0x000000011976e420
0x110effc80: 0x00007fff201d3af0 0x0000803000000000
0x110effc90: 0x0000000110f880c2 0x0000000000000002
0x110effca0: 0x0000000700000028 0x00000010000000a8

那么意味着TargetMetadata也就是TargetClassMetadata,那么我们认为的内存中的结构体也就是TargetClassMetadata。

TargetClassMetadata

那么在TargetClassMetadata中我们可以看到以下数据

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

那么它还有一个父类TargetAnyClassMetadata

TargetAnyClassMetadata

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

TargetAnyClassMetadata 继承了TargetHeapMetadata

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

TargetHeapMetadata又继承了TargetMetadata,TargetMetadata有一个属性kind。

经过源码的阅读我们可以得到

Metadata::Class
TargetClassMetadata(所有的属性)->TargetAnyClassMetadata(kind,superclass,cacheData)->TargetHeapMetadata->TargetMetadata(kind)

那么所有的这些就构成了我们Class内存结构(ClassMetadata + AnyClassMetadata + TargetMetadata),metadata数据结构体为:

struct swift_class_t{
        void *kind; //isa, kind(unsigned long)//如果和OC交互了就是isa指针
        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 HotpotCat {
    var name: String = "Hotpot"
    let age: Int = 1
}

var hotpot = HotpotCat()

对于HotpotCat中age、name来说都是我们的变量存储属性,在SIL中可以看到

class HotpotCat {
  @_hasStorage @_hasInitialValue var name: String { get set }
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @objc deinit
  init()
}
image.png

0x0000000100008188 存储的是metadata,0x0000000400000003存储的是refcount,0x0000746f70746f48 0xe600000000000000 存储的是String,0x0000000000000001存储的是Int。

计算属性

不占存储空间,本质是get/set方法
如果我们给计算属性赋值会发生什么呢?

class HotpotCat {
    var name: String = "Hotpot"
    var age: Int {
        get{
           3
        }
        set{
            age = newValue
        }
    }
}

var hotpot = HotpotCat()
hotpot.age = 6
print(hotpot.age)
image.png

可以看到发生了递归调用,自己调用自己。
那么写一个正确的计算属性

class HotpotCat {
    var width: Double = 10;//8字节
    var area: Double{//不占存储空间
        get{
            pow(width, 2)
        }
        set{
            width = sqrt(newValue)
        }
    }
}

var hotpot = HotpotCat()
print(class_getInstanceSize(HotpotCat.self))

输出

24

这也就证明计算属性确实不占内存空间。
我们生成sil文件看看计算属性到底是什么?

swiftc -emit-sil main.swift >> ./main.sil && open main.sil
class HotpotCat {
  @_hasStorage @_hasInitialValue var width: Double { get set }
  var area: Double { get set }
  @objc deinit
  init()
}
// HotpotCat.area.getter
sil hidden @$s4main9HotpotCatC4areaSdvg : $@convention(method) (@guaranteed HotpotCat) -> Double {
// %0 "self"                                      // users: %3, %2, %1
bb0(%0 : $HotpotCat):
  debug_value %0 : $HotpotCat, let, name "self", argno 1 // id: %1
  %2 = class_method %0 : $HotpotCat, #HotpotCat.width!getter : (HotpotCat) -> () -> Double, $@convention(method) (@guaranteed HotpotCat) -> Double // user: %3
  %3 = apply %2(%0) : $@convention(method) (@guaranteed HotpotCat) -> Double // user: %7
  %4 = float_literal $Builtin.FPIEEE64, 0x4000000000000000 // 2 // user: %5
  %5 = struct $Double (%4 : $Builtin.FPIEEE64)    // user: %7
  // function_ref pow
  %6 = function_ref @pow : $@convention(c) (Double, Double) -> Double // user: %7
  %7 = apply %6(%3, %5) : $@convention(c) (Double, Double) -> Double // user: %8
  return %7 : $Double                             // id: %8
} // end sil function '$s4main9HotpotCatC4areaSdvg'

// HotpotCat.area.setter
sil hidden @$s4main9HotpotCatC4areaSdvs : $@convention(method) (Double, @guaranteed HotpotCat) -> () {
// %0 "newValue"                                  // users: %5, %2
// %1 "self"                                      // users: %7, %6, %3
bb0(%0 : $Double, %1 : $HotpotCat):
  debug_value %0 : $Double, let, name "newValue", argno 1 // id: %2
  debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
  // function_ref sqrt
  %4 = function_ref @sqrt : $@convention(c) (Double) -> Double // user: %5
  %5 = apply %4(%0) : $@convention(c) (Double) -> Double // user: %7
  %6 = class_method %1 : $HotpotCat, #HotpotCat.width!setter : (HotpotCat) -> (Double) -> (), $@convention(method) (Double, @guaranteed HotpotCat) -> () // user: %7
  %7 = apply %6(%5, %1) : $@convention(method) (Double, @guaranteed HotpotCat) -> ()
  %8 = tuple ()                                   // user: %9
  return %8 : $()                                 // id: %9
} // end sil function '$s4main9HotpotCatC4areaSdvs'

属性观察者

willSet会在新值赋值之前调用,didSet会在新值赋值之后调用。

class HotpotCat {
    var name: String = "hotpot" {
        //新值存储前调用
        willSet{
            print("willSet newValue: \(newValue) oldValue: \(name)")
        }
        //新值存储后调用
        didSet{
            print("didSet oldValue: \(oldValue) newValue: \(name)")
        }
    }
}

var hotpot = HotpotCat()
hotpot.name = "cat"
willSet newValue: cat oldValue: hotpot
didSet oldValue: hotpot newValue: cat

查看一下SIL文件,我们在设置name的时候首先调用set方法,我们直接看SIL文件的name setter方法

属性观察者实现
这也就解释了为什么willSet能访问newValue和self,didSet能访问oldValue和self。
再继续看下willset(s4main9HotpotCatC4nameSSvw)方法的实现
sil private @$s4main9HotpotCatC4nameSSvw : $@convention(method) (@guaranteed String, @guaranteed HotpotCat) -> () {
// %0 "newValue"                                  // users: %31, %2
// %1 "self"                                      // users: %48, %3
bb0(%0 : $String, %1 : $HotpotCat):
  debug_value %0 : $String, let, name "newValue", argno 1 // id: %2
  debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
  %4 = integer_literal $Builtin.Word, 1 

可以看到newValue是编译器自己帮我们取得,let类型。didSet同理。
那我们自己指定变量名呢?

sil private @$s4main9HotpotCatC4nameSSvw : $@convention(method) (@guaranteed String, @guaranteed HotpotCat) -> () {
// %0 "myNewValue"                                // users: %31, %2
// %1 "self"                                      // users: %48, %3
bb0(%0 : $String, %1 : $HotpotCat):
  debug_value %0 : $String, let, name "myNewValue", argno 1 // id: %2
  debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
  %4 = integer_literal $Builtin.Word, 1           // user: %6

可以看到SIL里面已经变成我们自己起名的变量名。

那么如果我们再init方法里面调用self.name,属性观察者会被调用么?

class HotpotCat {
    var name: String = "hotpot" {
        //新值存储前调用
        willSet{
            print("willSet newValue: \(newValue) oldValue: \(name)")
        }
        //新值存储后调用
        didSet{
            print("didSet oldValue: \(oldValue) newValue: \(name)")
        }
    }
    init() {
        //不会调用属性观察者
        self.name = "cat"
    }
}

var hotpot = HotpotCat()

很明显控制台没有输出,为什么呢?
init方法是做初始化用的。在这个过程中访问oldValue或者其它属性会获取到未知状态的值,所以swift禁止操作,这也就是swift安全的体现。


image.png

我们再看一段有意思的代码

class HotpotCat {
    var name: String = "hotpot" {
        //新值存储前调用
        willSet {
            print("HotpotCat name willSet")
        }
        //新值存储后调用
        didSet {
            print("HotpotCat name didSet")
        }
    }
    var width: Double = 10
    var area: Double {
        get {
            print("HotpotCat area get")
            return pow(width, 2)
        }
        set {
            print("HotpotCat area set")
            width = sqrt(newValue)
        }
    }
}

class MyHotpotCat: HotpotCat {
    override var name: String {
        willSet {
            print("MyHotpotCat name willset")
        }
        didSet {
            print("MyHotpotCat name didSet")
        }
    }
    override var area: Double {
        willSet {
            print("MyHotpotCat area willset")
        }
        didSet {
            print("MyHotpotCat area didSet")
        }
    }
    override init() {
        super.init()
        self.name = "cat"
        self.area = 100
    }
}

var myHotpot = MyHotpotCat()

控制台打印

MyHotpotCat name willset
HotpotCat name willSet
HotpotCat name didSet
MyHotpotCat name didSet
MyHotpotCat area willset
HotpotCat area set
MyHotpotCat area didSet

这里要注意下name的调用顺序,可以简单理解为一个栈(先进后出)。


image.png

配置脚本生成SIL文件

每次跑命令生成SIL文件都比较麻烦,我们可以直接添加一个Target直接将命令放在脚本中执行:
TARGETS -> Other -> Aggregate

image.png
Build Phases -> + -> New Run Script Phase
image.png
#这里需要注意路径问题,在icloud里面的路径会报错,文件和路径替换为自己的工程文件和路径。
rm -rf ${SRCROOT}/main.sil
swiftc -emit-sil ${SRCROOT}/SwiftEnum/main.swift | xcrun swift-demangle  >> ./main.sil && open main.sil

附录

SIL参考文档
SIL手册
OC转Swift
参考阅读
https://swift.org/swift-compiler/#compiler-architecture
https://www.imooc.com/article/273543
https://blog.csdn.net/qq_41790914/article/details/106457729
https://www.jianshu.com/p/fb6923e3a7be
https://zhuanlan.zhihu.com/p/101898915
https://www.jianshu.com/p/440d760a7392?from=singlemessage
https://zhuanlan.zhihu.com/p/112465903
https://blog.csdn.net/wjnhub/article/details/107818429
枚举解析
LLVM相关内容:
https://zhuanlan.zhihu.com/p/102028114
https://zhuanlan.zhihu.com/p/102250532
https://zhuanlan.zhihu.com/p/102270840
https://zhuanlan.zhihu.com/p/102716482
https://zhuanlan.zhihu.com/p/103674744

上一篇下一篇

猜你喜欢

热点阅读