Swift 中的類與結構體

2022-01-23 iOS大全

本文結合源碼探究類和結構體的本質。

類和結構體的異同

Swift中,類和結構體有許多相似之處,但也有不同。

我們都知道,內存分配可以分為堆區(Heap)和棧區(Stack)。由於棧區內存是連續的,內存的分配和銷毀是通過入棧和出棧操作進行的,速度要高於堆區。堆區存儲高級數據類型,在數據初始化時,查找沒有使用的內存,銷毀時再從內存中清除,所以堆區的數據存儲不一定是連續的。

類(class)和結構體(struct)在內存分配上是不同的,基本數據類型和結構體默認分配在棧區,而像類這種高級數據類型存儲在堆區,且堆區數據存儲不是線程安全的,在頻繁的數據讀寫操作時,要進行加鎖操作。

結構體除了屬性的存儲更安全、效率更高之外,其函數的派發也更高效。由於結構體不能被繼承,也就是結構體的類型被final修飾,其內部函數屬於靜態派發,在編譯期就確定了函數的執行地址,其函數的調用通過內聯(inline)的方式進行優化,其內存連續,減少了函數的尋址及內存地址的偏移計算,其運行相比於動態派發更加高效。

另外, 引用技術也會對類的使用效率產生消耗,所以在可選的情況下應該儘可能的使用結構體。

結構體都是值類型, 當它被指定到常量或者變量,或者被傳遞給函數時會被拷貝的類型。實際上,Swift 中所有的基本類型:整數,浮點數,布爾量,字符串,數組和字典,還有枚舉,都是值類型,並且都以結構體的形式在後臺實現。這意味著字符串,數組和字典在它們被賦值到一個新的常量或者變量,亦或者它們本身被傳遞到一個函數或方法中的時候,其實是傳遞了值的拷貝。這不同於OC的NSString,NSArray和NSDictionary,他們是類,賦值和傳遞都是引用。

retain時不可避免要遍歷堆,而Swift的堆是通過雙向鍊表實現的,理論上可以減少retain時的遍歷,把效率提高一倍,但是還是比不過棧, 所以蘋果把一些放在堆裡的類型改成了值類型。

值類型存儲的是值,賦值時都是進行值拷貝,相互之間不會影響。而引用類型存儲的是對象的內存地址,賦值時拷貝指針,都是指向同一個對象(內存空間)。

類和結構體的異同:

相同點:都能定義屬性、方法、初始化器;都能添加extension擴展;都能遵循協議;

不同點:類是引用類型,存儲在堆區;結構體是值類型,存儲在棧區。類有繼承特性;結構體沒有。類實例可以被多次引用,有引用計數。類有反初始化器(析構函數)來釋放資源。類型轉換允許你在運行檢查和解釋一個類實例的類型。

結構體示例
struct Book {
    var name: String
    var high: Int
    func turnToPage(page:Int) {
        print("turn to page \(page)")
    }
}

var s = Book(name: "易經", high: 8)
var s1 = s
s1.high = 10
print(s.high, s1.high) // 8 10

這段代碼中初始化結構體high為18,賦值給s1時拷貝整個結構體,相當於s1是一個新的結構體,修改s1的high為10後,s的age仍然是8,s和s1互不影響。

通過 lldb 調試, 也能夠看出 s 和 s1 是不同的結構體. 一個在 0x0000000100008080, 一個在 0x0000000100008098.

(lldb) frame variable -L s
0x0000000100008080: (SwiftTest.Book) s = {
0x0000000100008080:   name = "易經"
0x0000000100008090:   high = 8
}
(lldb) frame variable -L s1
0x0000000100008098: (SwiftTest.Book) s1 = {
0x0000000100008098:   name = "易經"
0x00000001000080a8:   high = 10
}

類示例
class Person {
    var age: Int = 22
    var name: String?
    init(_ age: Int, _ name: String) {
        self.age = age
        self.name = name
    }
    func eat(food:String) {
        print("eat \(food)")
    }
    func jump() {
        print("jump")
    }
}

var c = Person(22, "jack")
var c1 = c
c1.age = 30
print(c.age, c1.age) // 30 30

如果是類,c1=c的時候拷貝指針,產生了一個新的引用,但都指向同一個對象,修改c1的age為30後,c的age也會變成30。

(lldb) frame variable -L c
scalar: (SwiftTest.Person) c = 0x0000000100679af0 {
0x0000000100679b00:   age = 30
0x0000000100679b08:   name = "jack"
}
(lldb) frame variable -L c1
scalar: (SwiftTest.Person) c1 = 0x0000000100679af0 {
0x0000000100679b00:   age = 30
0x0000000100679b08:   name = "jack"
}
(lldb) cat address 0x0000000100679af0
address:0x0000000100679af0, (String) $R1 = "0x100679af0 heap pointer, (0x30 bytes), zone: 0x7fff8076a000"

通過lldb調試,發現類的實例 c 和 c1 實際上是同一個對象, 再通過自定義命令 address 可以得出這個對象是在 heap 堆上.

而 c 和 c1 本身是2個不同的指針, 他們裡面都存的是 0x0000000100679af0 這個地址.

(lldb) po withUnsafePointer(to: &c, {print($0)})
0x0000000100008298
0 elements
(lldb) po withUnsafePointer(to: &c1, {print($0)})
0x00000001000082a0
0 elements

編譯過程clang編譯器

OC和C這類語言,會使用 clang 作為編譯器前端, 編譯成中間語言 IR, 再交給後端 LLVM 生成可執行文件.

Clang編譯過程有以下幾個缺點:

CFG(Control Flow Graph)缺少精準度Swift編譯器

為了解決這些缺點, Swift開發了專屬的Swift前端編譯器, 其中最關鍵的就是引入 SIL。

SIL

Swift Intermediate Language,Swift高級中間語言,Swift 編譯過程引入SIL有以下優點:

架起橋梁連接源碼與LLVM,減少源碼與LLVM之間的抽象鴻溝

SIL會對Swift進行高級別的語意分析和優化。像LLVM IR一樣,也具有諸如Module,Function和BasicBlock之類的結構。與LLVM IR不同,它具有更豐富的類型系統,有關循環和錯誤處理的信息仍然保留,並且虛函數表和類型信息以結構化形式保留。它旨在保留Swift的含義,以實現強大的錯誤檢測,內存管理等高級優化。

swift編譯步驟

Swift前端編譯器先把Swift代碼轉成SIL, 再轉成IR.

下面是每個步驟對應的命令和解釋

// 1 Parse: 語法分析組件, 從Swift源碼分析輸出抽象語法樹AST
swiftc main.swift -dump-parse   

// 2 語義分析組件: 對AST進行類型檢查,並對其進行類型信息注釋
swiftc main.swift -dump-ast   

// 3 SILGen組件: 生成中間體語言,未優化的 raw SIL (生SIL)
// 一系列在 生 SIL上運行的,用於確定優化和診斷合格,對不合格的代碼嵌入特定的語言診斷。
// 這些操作一定會執行,即使在`-Onone`選項下也不例外
swiftc main.swift -emit-silgen   

// 4 生成中間體語言(SIL),優化後的
// 一般情況下,是否在正式SIL上運行SIL優化是可選的,這個檢測可以提升結果可執行文件的性能.
// 可以通過優化級別來控制,在-Onone模式下不會執行.
swiftc main.swift -emit-sil   

// 5 IRGen會將正式SIL降級為 LLVM IR(.ll文件)
swiftc main.swift -emit-ir    

// 6 LLVM後端優化, 生成LLVM中間體語言 (.bc文件)
swiftc main.swift -emit-bc    

// 7 生成彙編
swiftc main.swift -emit-assembly 

// 8 生成二進位機器碼, 編譯成可執行.out文件
swiftc -o main.o main.swift    

一般我們在分析 sil 文件的時候,通過下面這條命令把 swift 文件直接轉成 sil 文件:

swiftc -emit-sil main.swift > main.sil

類的生命周期

下面分析一下類的創建過程, 如下代碼

class Human {
    var name: String
    init(_ name: String) {
        self.name = name
    }
    func eat(food:String) {
        print("eat \(food)")
    }
}

var h = Human("hali")

轉成sil, swiftc -emit-sil main.swift > human.sil

分析sil文件, 可以看到如下代碼, 是 __allocating_init 初始化方法

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

接下來在Xcode打上符號斷點 __allocating_init,

調用的是 swift_allocObject 這個方法, 而如果 Human繼承自NSObject, 會調用objc的 objc_allocWithZone 方法, 走OC的初始化流程.

分析Swift源碼[1], 搜索 swift_allocObject, 定位到 HeapObject.cpp 文件,

內部調用 swift_slowAlloc,

至此, 通過分析 sil, 彙編, 原始碼,我們可以得出swift對象的初始化過程如下:

__allocating_init -> swift_allocObject -> _swift_allocObject_ -> swift_slowAlloc -> Malloc

類的內存結構

通過上面的源碼, 發現初始化返回的是一個 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; // 8位元組

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS; //64位的位域信息, 8位元組; metadata 和 refCounts 一起構成默認16位元組實例對象的內存大小

#ifndef __swift__
 // .

#endif // __swift__
};

HeapObject的metadata是一個HeapMetadata類型, 本質上是 TargetHeapMetadata, 我們可以在源碼中找到這個定義

using HeapMetadata = TargetHeapMetadata<InProcess>;

再點擊跳轉到 TargetHeapMetadata,

template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> { //繼承自TargetMetadata
  using HeaderType = TargetHeapMetadataHeader<Runtime>;
// 下面是初始化
  TargetHeapMetadata() = default;
  constexpr TargetHeapMetadata(MetadataKind kind) // 純swift
    : TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP //和objc交互
  constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa) //isa
    : TargetMetadata<Runtime>(isa) {}
#endif
};

這裡可以看到, 如果是純swift,就會給入 kind, 如果是OC就給入 isa.

再繼續點擊跳轉分析 TargetHeapMetadata 的父類 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;//Kind成員變量
public:
 // .

  /// Get the nominal type descriptor if this metadata describes a nominal type,
  /// or return null if it does not.
  ConstTargetMetadataPointer<Runtime, TargetTypeContextDescriptor>
  getTypeContextDescriptor() const {
    switch (getKind()) { // 根據 kind 區分不同的類
    case MetadataKind::Class: {
      const auto cls = static_cast<const TargetClassMetadata<Runtime> *>(this);//把this強轉成TargetClassMetadata類型
      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;
    }
  }
 // .
};

TargetMetadata就是最終的基類, 其中有個 Kind 的成員變量, 它是一個固定值 0x7FF.

TargetMetadata 中根據 kind 種類強轉成其它類型, 所以 這個 TargetMetadata 就是所有元類型的基類.

在強轉成類的時候, 強轉類型是 TargetClassMetadata, 點擊跳轉然後分析它的繼承連如下

TargetClassMetadata : TargetAnyClassMetadata : TargetHeapMetadata : TargetMetadata

通過分析源碼, 可以得出關係圖

所以綜合繼承鏈上的成員變量, 可以得出類的內存結構:

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
}

PS: 補充kind種類, 這個是固定值

通過SIL分析異變方法

Class 和 struct 都可以定義方法,但是默認情況下,值類型不能被自身修改,也就意味著 struct方法不能修改自身的屬性。所以如下的代碼就會報錯 Left side of mutating operator isn't mutable: 'self' is immutable

struct Point {
    var x = 0.0, y = 0.0
    func moveBy(x deltaX: Double, y deltaY: Double) {
        self.x += deltaX //Left side of mutating operator isn't mutable: 'self' is immutable
        self.y += deltaY //Left side of mutating operator isn't mutable: 'self' is immutable
    }
}

此時在方法前面添加 mutating 關鍵字即可。

struct Point {
    var x = 0.0, y = 0.0
    func test() {
        print("test")
    }
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self.x += deltaX
        self.y += deltaY
    }
}

什麼是 mutating ?我們把代碼轉成 sil 來分析 swiftc -emit-sil main.swift > main.sil

// Point.test()
sil hidden @$s4main5PointV4testyyF : $@convention(method) (Point) -> () {
// %0 "self"                                      // user: %1
bb0(%0 : $Point):
  debug_value %0 : $Point, let, name "self", argno 1 // id: %1

與OC不同,Swift只有1個默認參數self,且作為最後一個參數傳入, 默認放在 x0 寄存器。debug_value 直接取值,不能被修改。

// Point.moveBy(x:y:)
sil hidden @$s4main5PointV6moveBy1x1yySd_SdtF : $@convention(method) (Double, Double, @inout Point) -> () {
// %0 "deltaX"                                    // users: %10, %3
// %1 "deltaY"                                    // users: %20, %4
// %2 "self"                                      // users: %16, %6, %5
bb0(%0 : $Double, %1 : $Double, %2 : $*Point):
  debug_value %0 : $Double, let, name "deltaX", argno 1 // id: %3
  debug_value %1 : $Double, let, name "deltaY", argno 2 // id: %4
  debug_value_addr %2 : $*Point, var, name "self", argno 3 // id: %5

比較上面2斷sil代碼,發現 mutating 的方法 moveBy 的默認參數self 多了一個 @inout修飾,它表示當前參數類型是間接的,傳遞的是已經初始化過的地址通過下面的 debug_value_addr 也可以看出, 取的是 *Point這個內容的地址,通過指針對self進行修改。

函數定義形參的時候,函數內參數的改變並不會影響外部, 但是在前面加上 inout 關鍵字就變成一個輸入輸出形式參數,在函數外部這些參數的改變將被保留.

方法調度Swift函數的3種派發機制

Swift有3種函數派發機制:

靜態派發 (static dispatch)

是在編譯期就能確定調用方法的派發方式, Swift中的靜態派發直接使用函數地址.

動態派發 (dynamic dispatch) / 虛函數表派發

動態派發是指編譯期無法確定應該調用哪個方法,需要在運行時才能確定方法的調用, 通過虛函數表查找函數地址再調用.

消息派發 (message dispatch)

使用objc的消息派發機制, objc採用了運行時objc_msgSend進行消息派發,所以Objc的一些動態特性在Swift裡面也可以被限制的使用。

靜態派發相比於動態派發更快,而且靜態派發還會進行內聯等一些優化,減少函數的尋址過程, 減少內存地址的偏移計算等一系列操作,使函數的執行速度更快,性能更高。

一般情況下, 不同類型的函數調度方式如下

類型調度方式extension值類型靜態派發靜態派發類函數表派發靜態派發NSObject 子類函數表派發靜態派發類函數的動態派發

通過一個案例探究 動態派發/虛函數表派發 表這種方式中, 程序是如何找到函數地址的

class LGTeacher {
  func teach(){
    print("teach")
  }
  func teach1(){
    print("teach1")
  }
  func teach2(){
    print("teach2")
  }
}
var t = LGTeacher()
t.teach()

在程序中, 斷點在函數處, 進入彙編代碼讀取寄存器匯中的值,

image-20220110120757351

這個 0x10004bab4 就是 teach() 函數的地址, 下面我們具體探究下中個地址是怎麼來的.

源碼的解讀

一般來講, Swift會把所有的方法都被存在類的虛表中, 我們可以在 sil 文件中發現這個 vtable.

根據之前的分析, 類的結構 TargetClassMetadata 有個屬性 Description, 這個是Swift類的描述TargetClassDescriptor.

  // 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.
  ConstTargetMetadataPointer<Runtime, TargetClassDescriptor> Description;

TargetClassDescriptor 它的內存結構如下

struct TargetClassDescriptor{ 
  var flags: UInt32 
  var parent: UInt32 
  var name: Int32 
  var accessFunctionPointer: Int32 
  var fieldDescriptor: Int32 
  var superClassType: Int32 
  var metadataNegativeSizeInWords: UInt32 
  var metadataPositiveSizeInWords: UInt32 
  var numImmediateMembers: UInt32 
  var numFields: UInt32 
  var fieldOffsetVectorOffset: UInt32 
  var Offset: UInt32 
  var size: UInt32 
  //V-Table 
}

在這個描述的開始到vtable之間的屬性有 13 ✖️ 4 = 52 字節,後面就是存儲方法描述TargetMethodDescriptor的 vtable 了。

struct TargetMethodDescriptor {
  /// Flags describing the method.
  MethodDescriptorFlags Flags; // 4位元組, 標識方法的種類, 初始化/getter/setter等等

  /// The method implementation.
  TargetRelativeDirectPointer<Runtime, void> Impl; // 相對地址, Offset 

  // TODO: add method types or anything else needed for reflection.
};

TargetMethodDescriptor 是對方法的描述, Flags表示方法的種類,佔據4個字節, Impl裡面並不是真正的方法imp, 而是一個相對偏移量,所以需要找到這個 TargetMethodDescriptor + 4位元組 + 相對偏移量 才能得到方法的真正地址。

可執行文件的解讀

在可執行文件中, Class、Struct、Enum 的 Discripter 地址信息一般存在 _TEXT,_swift5_types 段.

image-20220110114300935

iOS上一般小端模式, 所以我們讀到地址信息+偏移量 0xFFFFFBF4 + 0xBC68 = 0x10000B85C 得到 LGTeacher Description<TargetClassDescriptor> 在 MachO 中的地址. 虛擬內存的基地址是 0x100000000, 所以 B85C 就是 Description 的偏移量.

找到 B85C,

根據 TargetClassDescriptor 的內存結構,從 B85C 往後讀 52個字節就是 vtable,對應的偏移量 B890.

vtable是個數組,所以第一個元素 10 00 00 00 20 C2 FF FF 是 TargetMethodDescriptor, 再根據 TargetMethodDescriptor 的內存結構, 前面4位元組是Flags, 後面4位元組就是 Impl 的偏移量 Offset FFFFC220.

回到程序中,

通過 image list 輸出可執行文件加載的地址,其中第一個就是程序運行首地址,0x100044000 加上 v-table偏移量,就得到v-table在程序運行中的地址,也就是第一個函數 teach() 的 TargetMethodDescriptor的地址 0x100044000 + 0xB890 = 0x10004F890

然後加上 Flags 的4位元組,0x10004F890 + 0x4 = 0x10004F894 得到 Impl,

加上Offset再減去虛擬內存基地址 0x10004F894 + 0xFFFFC220 - 0x100000000 = 0x10004BAB4

才得到函數地址 0x10004BAB4 .

Struct函數靜態派發
struct LGTeacher {
  func teach(){
    print("teach")
  }
  func teach1(){
    print("teach1")
  }
  func teach2(){
    print("teach2")
  }
}
var t = LGTeacher()
t.teach()

上述案例中改為 Struct, 那麼就是直接調用的函數地址, 屬於靜態派發.

extension

不論是 Class 或者 Struct, extension裡的函數都是靜態派發, 無法在運行時做任何替換和改變, 因為其裡面的方法都是在編譯期確定好的, 程序中以硬編碼的方式存在, 不會放在vtable中.

extension LGTeacher{
 func teach3(){
    print("teach3")
  }


var t = LGTeacher()
t.teach3()

都是直接調用函數地址

所以, 無法通過 extension 支持多態.

那麼為什麼 Swift 會把 extension 設計成靜態的呢?

OC中子類繼承後不重寫方法的話是去父類中找方法實現, 但是 Swift類在繼承的時候, 是把父類的方法形成一張vtable存在自己身上,這樣做也是為了節省方法的查找時間, 如果想讓 extension 加到 vtable 中, 並不是直接在子類vtable的最後直接追加就可以的, 需要在子類中記錄下父類方法的index,把父類的extension方法插入到子類vtable中父類方法index後相鄰的位置,再把子類自己的方法往後移動,這樣的一番操作消耗是很大的.

關鍵字最派發方式的影響

不同的函數修飾關鍵字對派發方式也有這不同的影響

final

final:添加了 final 關鍵字的函數無法被重寫/繼承,使用靜態派發,不會在 vtable 中出現,且對 objc 運行時不可見。

dynamic

dynamic: 函數均可添加 dynamic 關鍵字,為非objc類和值類型的函數賦予動態性,但派發方式還是函數表派發。

class LGTeacher {
  dynamic func teach(){
    print("teach")
  }
}
extension LGTeacher {
    @_dynamicReplacement(for: teach())
    func teach3() {
        print("teach3")
    }
}
var t = LGTeacher()
t.teach3() // teach3
t.teach()  // teach3

如上代碼中, teach() 函數是函數表派發, 存在 vtable, 並且 dynamic 賦予動態性, 與 @_dynamicReplacement(for: teach()) 關鍵字配合使用, 把 teach()函數的實現改為 teach3()的實現, 相當於OC中把 teach()的SEL對應為teach3()的imp, 實現方法的替換.

這個具體的實現是 llvm 編譯器處理的, 在中間語言 IR 中, teach() 函數中有2個分支, 一個 original, 一個 forward, 如果我們有替換的函數, 就走 forward 分支.

# 轉成 IR 中間語言 .ll 文件
swiftc -emit-ir main.swift > dynamic.ll 

@objc

@objc:該關鍵字可以將Swift函數暴露給Objc運行時,依舊是函數表派發。

@objc dynamic

@objc dynamic:消息派發的方式, 和 OC 一樣. 實際開發中, Swift 和 OC 交互大多會使用這種方式.

對於純Swift類, @objc dynamic 可以讓方法和OC一樣使用 Runtime API.

如果需要和OC進行交互, 需要把類繼承自 NSObjec.

參考資料

《Swift高級進階班》[2]

GitHub: apple[3] - swift源碼[4]

《跟戴銘學iOS編程: 理順核心知識點》

《程式設計師的自我修養》

Swift程式語言 - 類和結構體[5]

Swift Intermediate Language 初探[6]

Swift性能高效的原因深入分析[7]

Swift編譯器中間碼SIL[8]

Swift的高級中間語言:SIL[9]

參考資料[1]

Swift源碼: https://github.com/apple/swift

[2]

《Swift高級進階班》: https://ke.qq.com/course/3202559

[3]

apple: https://github.com/apple

[4]

swift源碼: https://github.com/apple/swift

[5]

類和結構體: https://www.cnswift.org/classes-and-structures#spl

[6]

Swift Intermediate Language 初探: https://zhuanlan.zhihu.com/p/101898915

[7]

Swift性能高效的原因深入分析: http://www.codebaoku.com/it-swift/it-swift-198322.html

[8]

Swift編譯器中間碼SIL: https://woshiccm.github.io/posts/Swift編譯器中間碼SIL/#sil簡介

[9]

Swift的高級中間語言:SIL: https://www.jianshu.com/p/c2880460c6cd

相關焦點

  • Friday Q&A 2016-01-29: Swift 的結構體存儲"
    譯者:ray16897188;校對:Channe;定稿:千葉知風Swift 的類對大多數剛接觸程式語言的人來說是很容易理解的,它們和其他語言中的類十分類似
  • Swift中編寫單例的正確方式
    這些連結中已經有很詳盡的描述,比如https://github.com/hpique/SwiftSingleton,http://stackoverflow.com/questions/24024549/dispatch-once-singleton-model-in-swift和https://developer.apple.com/swift/blog/?id=7。
  • iOS面試題-Swift篇
    類(class) 和 結構體(struct) 有什麼區別?在 Swift 中,class 是引用類型(指針類型), struct 是值類型值類型在傳遞和賦值時將進行複製; 賦值給var、let或者給函數傳參,是直接將所有內容拷貝一份, 類似於對文件進行copy、paste操作,產生了全新的文件副本。
  • 面試問:C#結構體和類的區別,結構體的使用場景
    昨天發布了一篇關於結構體的文章,但是對結構體的用法還比較模糊,這篇文章將介紹結構體和類的區別,以及結構體的使用
  • 來一次有側重點的區分Swift與Objective-C
    、面向對象編程,OC注重面向對象編程- swift注重值類型,OC注重指針和引用- swift是靜態類型語言,OC是動態類型語言- swift容易閱讀,文件結構和大部分語法簡易化,只有.swift文件,結尾不需要分號- swift中的可選類型,是用於所有數據類型,而不僅僅局限於類。
  • 結構體
    結構體概述在前面章節中介紹了 C 語言中的多種數據類型,例如:整型、字符型、浮點型、數組、指針……等等。但是在實際開發中,只有這些數據類型是不夠的,難以勝任複雜的程序設計。例如:在員工信息管理系統中,員工的信息就是一類複雜的數據。每條記錄中都包括員工的姓名、性別、年齡、工號、工資等信息。姓名為字符數組、性別為字符、年齡為整型、工號為整型、工資為整型。
  • 如何才能編寫高性能的 Swift 代碼
    final 關鍵字是一個類、一個方法、或一個屬性聲明中的一個限制,使得這樣的聲明不得被重寫。這意味著編譯器可以呼叫直接的函數調用代替間接調用。例如下面的 C.array1 和 D.array1 將會被直接[3]訪問。
  • 盤點開發者最喜愛的Swift技巧
    在本文中,來自objc.io、LinkedIn等各個公司的軟體工程師及資深開發者總結了自己在接觸Swift過程中最喜愛的貼士與技巧,有些技巧即使是很早就入坑Swift的開發者都不知道,不妨一起來了解一下吧。
  • 如何設計結構體
    》,今天這裡繼續聊聊如何設計結構體,注意本文不介紹在C++中結構體和類具體有什麼區別,本文所說的結構體是指只有數據欄位不帶任何函數的那種結構體。這種內存對齊可能會在成員大小混合的結構體中產生未使用字節的空洞。
  • 【Go】內存中的結構體
    因為結構體的各個欄位類型不同,有大有小,而結構體在存儲時通常需要進行內存對齊,所以結構體在存儲時可能會出現"空洞",也就是無法使用到的內存空間.在之前的Go系列文章中,我們接觸最多的結構體是reflect包中的rtype,可以說已經非常熟悉.
  • C語言中的結構體和聯合體
    由於結構體將一組相關變量看作一個單元而不是各自獨立的實體,因此結構體有助於組織複雜的數據,特別是在大型的程序中。共用體(union),也稱為聯合體,是用於(在不同時刻)保存不同類型和長度的變量,它提供了一種方式,以在單塊存儲區中管理不同類型的數據。今天,我們來介紹一下 C 語言中結構體和共用體的相關概念和使用。
  • 【swift開發】關於Swift中Struct,Class和Enum的那些事兒
    Swift標準庫中的絕大多數類型都是struct,甚至Foundation中的一些類也提供了它們在Swift中的struct版本,而class和enum只佔很少一部分。Class,Struct and Enum對比表共同點:
  • 從Mach-O角度談談Swift和OC的存儲差異
    歸根到底還是由於Mach-O文件存儲了類和函數的信息。在Mach-O中,所有的類都存儲到__objc_classlist這個section中。通過 __objc_classlist中的地址,我們能找到每個類的詳細信息。本文以arm64架構為例,在找到0x11820文件偏移後,我們很容易通過結構體結構套取到類的信息。
  • LabVIEW中結構體控制項編程實例
    1、結構體簡介LabVIEW中的結構體(中文版翻譯為「簇」)是常用的一種數據結構類型,一個結構體控制項中可以包含任意多個任意類型的不同元素。與數組不同的地方在於,它包含的元素數據類型可以相同,也可以不同,而數組中只能包含相同類型的多個元素。
  • C語言中結構體struct的用法
    #include <stdio.h>int main() { struct {    char *name;   int age;     char group;   } stu1;   stu1.name = "Tom"; stu1.age = 18;  stu1.group = 'A'; printf("%s
  • Swift 語言學習及速查手冊
    代碼: https://github.com/coderzh/CodeTips/blob/master/swift.swift/* swift.swift: Swift 速學速查速用代碼手冊 Source: github.com/coderzh/CodeTips/blob/master/swift.swift Author: coderzh(github.com/coderzh
  • 簡單介紹 Swift on Fedora ——在 Fedora 中使用 Swift
    下面將和大家簡單介紹一下 Swift 以及如何在 Fedora 中使用它。安全,快速,富有表現力和許多現代程式語言一樣,Swift 旨在設計得比基於 C 的程式語言更安全。例如,變量總是在可以使用之前初始化、檢查數組和整數是否存在溢出、內存自動管理等。Swift 將意圖放在語法中。
  • C語言 | 結構體變量
    「要成為絕世高手,並非一朝一夕,除非是天生武學奇才,但是這種人…萬中無一
  • Golang 入門 : 結構體(struct)
    結構體由一系列命名的元素組成,這些元素又被稱為欄位,每個欄位都有一個名稱和一個類型。結構體的目的就是把數據聚集在一起,以便能夠更加便捷地操作這些數據。結構體的概念在 C 語言裡很常見,被稱為 struct。Golang 中的結構體也是 struct。Go 語言中沒有類的概念,因此在 Go 中結構體有著更為重要的地位。
  • C 語言中的結構體和共用體(聯合體)
    在 C 語言中,結構體(struct)是一個或多個變量的集合,這些變量可能為不同的類型,為了處理的方便而將這些變量組織在一個名字之下。由於結構體將一組相關變量看作一個單元而不是各自獨立的實體,因此結構體有助於組織複雜的數據,特別是在大型的程序中。