Swift 泛型

前言

本篇文章將分析Swift中最后一個(gè)重要的知識(shí)點(diǎn) ?? 泛型,首先介紹一下概念,然后講解常用基礎(chǔ)的語法,最后重點(diǎn)分析下泛型函數(shù),主要是從IR代碼層面分析下泛型函數(shù)的調(diào)用流程。

一、泛型的概念

首先說一下泛型的概念 ??

  1. 泛型代碼能根據(jù)所定義的要求寫出可以用于任何類型靈活的、可復(fù)用的函數(shù)。可以編寫出可復(fù)用、意圖表達(dá)清晰、抽象的代碼。
  2. 泛型是Swift最強(qiáng)大的特性之一,很多Swift標(biāo)準(zhǔn)庫(kù)基于泛型代碼構(gòu)建的。
    例如,SwiftArrayDictionary類型都是泛型集合??梢詣?chuàng)建一個(gè)容納 Int 值的數(shù)組,或者容納 String 值的數(shù)組,甚至容納任何 Swift 可以創(chuàng)建的其他類型的數(shù)組。同樣,可以創(chuàng)建一個(gè)存儲(chǔ)任何指定類型值的字典,而且類型沒有限制。
  3. 泛型所解決的問題:代碼的復(fù)用性和抽象能力
    比如交換兩個(gè)值,這里的值可以是Int、Double、String。

例如下面的例子,其中的T就是泛型??

func test<T>(_ a: T, _ b: T)->Bool{
    return a == b
}

//經(jīng)典例子swap,使用泛型,可以滿足不同類型參數(shù)的調(diào)用
func swap<T>(_ a: inout T, _ b: inout T){
    let tmp = a
    a = b
    b = tmp
}

二、泛型的基礎(chǔ)語法

接著我們來看看泛型的基礎(chǔ)用法,主要講3點(diǎn)??

  • 類型約束
  • 關(guān)聯(lián)類型
  • Where語句

2.1 類型約束

在一個(gè)類型參數(shù)后面放置協(xié)議或者是類,例如下面的例子,要求類型參數(shù)T遵循Equatable協(xié)議??

func test<T: Equatable>(_ a: T, _ b: T)->Bool{
    return a == b
}

2.2 關(guān)聯(lián)類型

在定義協(xié)議時(shí),使用關(guān)聯(lián)類型給協(xié)議中用到的類型起一個(gè)占位符名稱。關(guān)聯(lián)類型只能用于協(xié)議,并且是通過關(guān)鍵字associatedtype指定。
首先我們來看看下面這個(gè)示例,仿寫的一個(gè)的結(jié)構(gòu)體??

struct LGStack {
    private var items = [Int]()
    
    mutating func push(_ item: Int){
        items.append(item)
    }
    
    mutating func pop() -> Int?{
        if items.isEmpty {
            return nil
        }
        return items.removeLast()
    }
}

該結(jié)構(gòu)體中有個(gè)成員Item,是個(gè)數(shù)組,當(dāng)前只能存儲(chǔ)Int類型的數(shù)據(jù),如果想使用其他類型呢??? 可以通過協(xié)議來實(shí)現(xiàn) ??

protocol LGStackProtocol {
    //協(xié)議中使用類型的占位符
    associatedtype Item
}
struct LGStack: LGStackProtocol{
    //在使用時(shí),需要指定具體的類型
    typealias Item = Int
    private var items = [Item]()
    
    mutating func push(_ item: Item){
        items.append(item)
    }
    
    mutating func pop() -> Item?{
        if items.isEmpty {
            return nil
        }
        return items.removeLast()
    }
}

此時(shí)在協(xié)議LGStackProtocol中就用到了associatedtype關(guān)鍵字,先讓Item占個(gè)位,然后在類LGStack遵循協(xié)議后使用typealias關(guān)鍵字指定Item的具體類型。當(dāng)然,我們這個(gè)時(shí)候也可以寫一個(gè)泛型的版本??

struct LGStack<Element> {
    private var items = [Element]()
    
    mutating func push(_ item: Element){
        items.append(item)
    }
    
    mutating func pop() -> Element?{
        if items.isEmpty {
            return nil
        }
        return items.removeLast()
    }
}

2.3 Where語句

where語句主要用于表明泛型需要滿足的條件,即限制形式參數(shù)的要求??

protocol LGStackProtocol {
    //協(xié)議中使用類型的占位符
    associatedtype Item
    var itemCount: Int {get}
    mutating func pop() -> Item?
    func index(of index: Int) -> Item
}
struct LGStack: LGStackProtocol{
    //在使用時(shí),需要指定具體的類型
    typealias Item = Int
    private var items = [Item]()
    
    var itemCount: Int{
        get{
            return items.count
        }
    }

    mutating func push(_ item: Item){
        items.append(item)
    }

    mutating func pop() -> Item?{
        if items.isEmpty {
            return nil
        }
        return items.removeLast()
    }
    
    func index(of index: Int) -> Item {
        return items[index]
    }
}
/*
 where語句
 - T1.Item == T2.Item 表示T1和T2中的類型必須相等
 - T1.Item: Equatable 表示T1的類型必須遵循Equatable協(xié)議,意味著T2也要遵循Equatable協(xié)議
 */
func compare<T1: LGStackProtocol, T2: LGStackProtocol>(_ stack1: T1, _ stack2: T2) -> Bool where T1.Item == T2.Item, T1.Item: Equatable{
    guard stack1.itemCount == stack2.itemCount else {
        return false
    }
    
    for i in 0..<stack1.itemCount {
        if stack1.index(of: i) !=  stack2.index(of: i){
            return false
        }
    }
    return true
}

還可以這么寫??

extension LGStackProtocol where Item: Equatable{}
  • 當(dāng)希望泛型指定類型時(shí)擁有特定功能,可以這么寫??(在上述寫法的基礎(chǔ)上增加extension)
extension LGStackProtocol where Item == Int{
    func test(){
        print("test")
    }
}
var s = LGStack()
s.test()

其中的test()就是你自定義的功能。

注意:如果將where后的Int改成Double類型,是無法找到test函數(shù)的!

三、泛型函數(shù)

我們?cè)谏厦娼榻B了泛型的基本語法,接下來我們來分析下泛型的底層原理。先看示例??

//簡(jiǎn)單的泛型函數(shù)
func testGenric<T>(_ value: T) -> T{
    let tmp = value
    return tmp
}

class LGTeacher {
    var age: Int = 18
    var name: String = "Kody"
}

//傳入Int類型
testGenric(10)
//傳入元組
testGenric((10, 20))
//傳入實(shí)例對(duì)象
testGenric(LGTeacher())

從上面的代碼中可以看出,泛型函數(shù)可以接受任何類型。那么問題來了??

泛型是如何區(qū)分不同的參數(shù),來管理不同類型的內(nèi)存呢?

老辦法,查看IR代碼??

至此我們知道,當(dāng)前泛型通過VWT來進(jìn)行內(nèi)存操作。

3.1 VWT

看下VWT的源碼(在Metadata.hTargetValueWitnessTable)??

/// A value-witness table.  A value witness table is built around
/// the requirements of some specific type.  The information in
/// a value-witness table is intended to be sufficient to lay out
/// and manipulate values of an arbitrary type.
template <typename Runtime> struct TargetValueWitnessTable {
  // For the meaning of all of these witnesses, consult the comments
  // on their associated typedefs, above.

#define WANT_ONLY_REQUIRED_VALUE_WITNESSES
#define VALUE_WITNESS(LOWER_ID, UPPER_ID) \
  typename TargetValueWitnessTypes<Runtime>::LOWER_ID LOWER_ID;
#define FUNCTION_VALUE_WITNESS(LOWER_ID, UPPER_ID, RET, PARAMS) \
  typename TargetValueWitnessTypes<Runtime>::LOWER_ID LOWER_ID;

#include "swift/ABI/ValueWitness.def"

  using StoredSize = typename Runtime::StoredSize;

  /// Is the external type layout of this type incomplete?
  bool isIncomplete() const {
    return flags.isIncomplete();
  }

  /// Would values of a type with the given layout requirements be
  /// allocated inline?
  static bool isValueInline(bool isBitwiseTakable, StoredSize size,
                            StoredSize alignment) {
    return (isBitwiseTakable && size <= sizeof(TargetValueBuffer<Runtime>) &&
            alignment <= alignof(TargetValueBuffer<Runtime>));
  }

  /// Are values of this type allocated inline?
  bool isValueInline() const {
    return flags.isInlineStorage();
  }

  /// Is this type POD?
  bool isPOD() const {
    return flags.isPOD();
  }

  /// Is this type bitwise-takable?
  bool isBitwiseTakable() const {
    return flags.isBitwiseTakable();
  }

  /// Return the size of this type.  Unlike in C, this has not been
  /// padded up to the alignment; that value is maintained as
  /// 'stride'.
  StoredSize getSize() const {
    return size;
  }

  /// Return the stride of this type.  This is the size rounded up to
  /// be a multiple of the alignment.
  StoredSize getStride() const {
    return stride;
  }

  /// Return the alignment required by this type, in bytes.
  StoredSize getAlignment() const {
    return flags.getAlignment();
  }

  /// The alignment mask of this type.  An offset may be rounded up to
  /// the required alignment by adding this mask and masking by its
  /// bit-negation.
  ///
  /// For example, if the type needs to be 8-byte aligned, the value
  /// of this witness is 0x7.
  StoredSize getAlignmentMask() const {
    return flags.getAlignmentMask();
  }
  
  /// The number of extra inhabitants, that is, bit patterns that do not form
  /// valid values of the type, in this type's binary representation.
  unsigned getNumExtraInhabitants() const {
    return extraInhabitantCount;
  }

  /// Assert that this value witness table is an enum value witness table
  /// and return it as such.
  ///
  /// This has an awful name because it's supposed to be internal to
  /// this file.  Code outside this file should use LLVM's cast/dyn_cast.
  /// We don't want to use those here because we need to avoid accidentally
  /// introducing ABI dependencies on LLVM structures.
  const struct EnumValueWitnessTable *_asEVWT() const;

  /// Get the type layout record within this value witness table.
  const TypeLayout *getTypeLayout() const {
    return reinterpret_cast<const TypeLayout *>(&size);
  }

  /// Check whether this metadata is complete.
  bool checkIsComplete() const;

  /// "Publish" the layout of this type to other threads.  All other stores
  /// to the value witness table (including its extended header) should have
  /// happened before this is called.
  void publishLayout(const TypeLayout &layout);
};

很明了,VWT中存放的是 size(大小)、alignment(對(duì)齊方式)、stride(步長(zhǎng)),大致結(jié)構(gòu)圖??

所以metadata中都存放了VWT來管理類型的值。比如Int、String、Class的復(fù)制銷毀、創(chuàng)建以及是否需要引用計(jì)數(shù)。

再回過頭來看看上面示例的IR代碼,其實(shí)執(zhí)行的流程大致如下??

  1. 詢問metadataVWT:size,stride分配內(nèi)存空間
  2. 初始化temp
  3. 調(diào)用VWT-copy方法拷貝值到temp
  4. 返回temp
  5. 調(diào)用VWT-destory方法銷毀局部變量

所以??

泛型在整個(gè)運(yùn)行過程中的關(guān)鍵依賴于metadata。

3.2 源碼調(diào)試

主要分為2類調(diào)試:值類型和引用類型。

3.2.1 值類型的調(diào)試

首先打上斷點(diǎn)??

打開匯編??

運(yùn)行??

然后,我們?nèi)wift源碼中查找NativeBox(在metadataimpl.h源碼中)??

對(duì)于值類型通過內(nèi)存copy和move進(jìn)行內(nèi)存處理。

3.2.2 引用類型的調(diào)試

同理,引用類型也是先打上斷點(diǎn),查看匯編 ??

/// A box implementation class for Swift object pointers.
struct SwiftRetainableBox :
    RetainableBoxBase<SwiftRetainableBox, HeapObject*> {
  static HeapObject *retain(HeapObject *obj) {
    if (isAtomic) {
      swift_retain(obj);
    } else {
      swift_nonatomic_retain(obj);
    }
    return obj;
  }

  static void release(HeapObject *obj) {
    if (isAtomic) {
      swift_release(obj);
    } else {
      swift_nonatomic_release(obj);
    }
  }
};

SwiftRetainableBox繼承RetainableBoxBase??

/// A CRTP base class for defining boxes of retainable pointers.
template <class Impl, class T> struct RetainableBoxBase {
  using type = T;
  static constexpr size_t size = sizeof(T);
  static constexpr size_t alignment = alignof(T);
  static constexpr size_t stride = sizeof(T);
  static constexpr bool isPOD = false;
  static constexpr bool isBitwiseTakable = true;
#ifdef SWIFT_STDLIB_USE_NONATOMIC_RC
  static constexpr bool isAtomic = false;
#else
  static constexpr bool isAtomic = true;
#endif

  static void destroy(T *addr) {
    Impl::release(*addr);
  }

  static T *initializeWithCopy(T *dest, T *src) {
    *dest = Impl::retain(*src);
    return dest;
  }

  static T *initializeWithTake(T *dest, T *src) {
    *dest = *src;
    return dest;
  }
  
  static T *assignWithCopy(T *dest, T *src) {
    T oldValue = *dest;
    *dest = Impl::retain(*src);
    Impl::release(oldValue);
    return dest;
  }

  static T *assignWithTake(T *dest, T *src) {
    T oldValue = *dest;
    *dest = *src;
    Impl::release(oldValue);
    return dest;
  }

  // Right now, all object pointers are brought down to the least
  // common denominator for extra inhabitants, so that we don't have
  // to worry about e.g. type substitution on an enum type
  // fundamentally changing the layout.
  static constexpr unsigned numExtraInhabitants =
    swift_getHeapObjectExtraInhabitantCount();

  static void storeExtraInhabitantTag(T *dest, unsigned tag) {
    swift_storeHeapObjectExtraInhabitant((HeapObject**) dest, tag - 1);
  }

  static unsigned getExtraInhabitantTag(const T *src) {
    return swift_getHeapObjectExtraInhabitantIndex((HeapObject* const *) src) +1;
  }
};

所以,引用類型的處理中也包含了destroy initializeWithCopyinitializeWithTake。再回過頭來看??

所以??

對(duì)于引用類型,會(huì)調(diào)用retain進(jìn)行引用計(jì)數(shù)+1,處理完在調(diào)用destory,而destory中是調(diào)用release進(jìn)行引用計(jì)數(shù)-1。

小結(jié)
  • 對(duì)于一個(gè)值類型,例如Integer??

    1、該類型的copy和move操作會(huì)進(jìn)行內(nèi)存拷貝
    2、destory操作則不進(jìn)行任何操作

  • 對(duì)于一個(gè)引用類型,如class??

    1、該類型的copy操作會(huì)對(duì)引用計(jì)數(shù)+1,
    2、move操作會(huì)拷貝指針,而不會(huì)更新引用計(jì)數(shù)
    3、destory操作會(huì)對(duì)引用計(jì)數(shù)-1

3.3 方法作為類型

還有一種場(chǎng)景 ?? 如果把一個(gè)方法當(dāng)做泛型類型傳遞進(jìn)去呢?例如??

func makeIncrementer() -> (Int) -> Int {
    var runningTotal = 10
    return {
        runningTotal += $0
        return runningTotal
    }
}

func test<T>(_ value: T) {

}

let makeInc = makeIncrementer()
test(makeInc)

我們還是看IR??

流程并不復(fù)雜,我們可以通過內(nèi)存綁定仿寫這個(gè)過程??

仿寫
struct HeapObject {
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refcount2: UInt32
}

struct Box<T> {
    var refCounted:HeapObject
    var value: T //捕獲值
}

struct FunctionData<BoxType> {
    var ptr: UnsafeRawPointer //內(nèi)嵌函數(shù)地址
    var captureValue: UnsafePointer<BoxType>? //捕獲值地址
}

struct TestData<T> {
    var  ref: HeapObject
    var function: FunctionData<T>
}

func makeIncrementer() -> (Int) -> Int {
    var runningTotal = 10
    return {
        runningTotal += $0
        return runningTotal
    }
}

func test<T>(_ value: T) {
    let ptr  = UnsafeMutablePointer<T>.allocate(capacity: 1)
    ptr.initialize(to: value)
    //對(duì)于泛型T來說做了一層TestData橋接,目的是為了能夠更好的解決不同值傳遞
    let ctx = ptr.withMemoryRebound(to: FunctionData<TestData<Box<Int>>>.self, capacity: 1) {
        $0.pointee.captureValue?.pointee.function.captureValue!
    }
    
    print(ctx?.pointee.value)
    ptr.deinitialize(count: 1)
    ptr.deallocate()
}

//{i8 *, swift type *}
let makeInc = makeIncrementer()
test(makeInc)

運(yùn)行??

對(duì)于泛型T來說做了一層TestData橋接,目的是為了能夠更好的解決不同值傳遞。

總結(jié)

本篇文章重點(diǎn)分析了Swift泛型基礎(chǔ)語法IR底層的處理流程,分別分析了值類型、引用類型函數(shù)入?yún)⒌膱?chǎng)景,希望大家能夠掌握。至此,Swift的知識(shí)點(diǎn)均已覆蓋完畢,感謝大家的支持!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift 進(jìn)階之路 文章匯總[http://www.itdecent.cn/p/5fbedf309237] 本...
    Style_月月閱讀 949評(píng)論 1 3
  • 協(xié)議為方法、屬性、以及其他特定的任務(wù)需求或功能定義藍(lán)圖。協(xié)議可被類、結(jié)構(gòu)體、或枚舉類型采納以提供所需功能的具體實(shí)現(xiàn)...
    HotPotCat閱讀 1,746評(píng)論 2 5
  • 泛型代碼讓你能根據(jù)自定義的需求,編寫出適用于任意類型的、靈活可復(fù)用的函數(shù)及類型。 你可避免編寫重復(fù)的代碼,而是用一...
    DevXue閱讀 263評(píng)論 0 0
  • Swift 提供了泛型讓你寫出靈活且可重用的函數(shù)和類型。Swift 標(biāo)準(zhǔn)庫(kù)是通過泛型代碼構(gòu)建出來的。Swift 的...
    零度_不結(jié)冰閱讀 441評(píng)論 0 0
  • 泛型: 泛型是一種類型的占位符,具體的類型將會(huì)在之后被填充。由于Swift的嚴(yán)格類型檢驗(yàn),這是很有用的。在不能或者...
    小松樹先生閱讀 771評(píng)論 0 3

友情鏈接更多精彩內(nèi)容