Swift泛型

泛型Swift 最強大的特性之一,oc轉Swift的需要重點學習一下。
① 泛型代碼能根據(jù)所定義的要求寫出可以用于任何類型的靈活的、可復用的函數(shù)。可以編寫出可復用、意圖表達清晰、抽象的代碼。
② 泛型是Swift最強大的特性之一,很多Swift標準庫是基于泛型代碼構建的。如,Swift 的ArrayDictionary類型都是泛型集合;你也可以創(chuàng)建一個容納 Int值的數(shù)組,或者容納 String 值的數(shù)組,甚至容納任何 Swift 可以創(chuàng)建的其他類型的數(shù)組。同樣,可以創(chuàng)建一個存儲任何指定類型值的字典,而且類型沒有限制。
③ 泛型所解決的問題:代碼的復用性和抽象能力。比如,交換兩個值,這里的值可以是IntDoubleString

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

一、基礎語法

主要講3點:類型約束關聯(lián)類型Where語句

1.1 類型約束

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

func test<T: Equatable>(_ a: T, _ b: T)->Bool{
    return a == b
}
1.2 關聯(lián)類型

在定義協(xié)議時,使用關聯(lián)類型給協(xié)議中用到的類型起一個占位符名稱。關聯(lián)類型只能用于協(xié)議,并且是通過關鍵字associatedtype指定。

下面這個示例,仿寫的一個棧的結構體

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

該結構體中有個成員Item,是個只能存儲Int類型的數(shù)組,如果想使用其他類型呢? 可以通過協(xié)議來實現(xiàn) :

protocol LLStackProtocol {
    //協(xié)議中使用類型的占位符
    associatedtype Item
}
struct LLStack: LLStackProtocol{
    //在使用時,需要指定具體的類型
    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()
    }
}

我們在嘗試用泛型實現(xiàn)上面的功能:

struct LLStack<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()
    }
}

泛型的優(yōu)勢和強大,暴露無疑。

2.3 Where語句

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

protocol LLStackProtocol {
    //協(xié)議中使用類型的占位符
    associatedtype Item
    var itemCount: Int {get}
    mutating func pop() -> Item?
    func index(of index: Int) -> Item
}
struct LLStack: LLStackProtocol{
    //在使用時,需要指定具體的類型
    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: LLStackProtocol, T2: LLStackProtocol>(_ 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 LLStackProtocol where Item: Equatable{}

當希望泛型指定類型時擁有特定功能,可以這么寫,在上述寫法的基礎上增加extension

extension LLStackProtocol where Item == Int{
    func test(){
        print("test")
    }
}
var s = LGStack()

泛型函數(shù)

簡單示例:

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

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

//傳入Int類型
testGenric(10)
//傳入元組
testGenric((10, 20))
//傳入實例對象
testGenric(Teacher())

從以上代碼可以看出,泛型函數(shù)可以接受任何類型

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

查看SIL代碼,并沒有什么內存相關的信息。查看IR代碼,從中可以得出VWT中存放的是 size(大小)alignment(對齊方式)stride(步長)destorycopy(函數(shù))

2.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(對齊方式)、stride(步長),大致結構圖:

VWT.png

metadata中存放了VWT來管理類型的值。
回過頭,示例的IR代碼執(zhí)行的流程大致如下:詢問metadataVWT:size,stride分配內存空間,初始化temp,調用VWT-copy方法拷貝值到temp,返回temp,調用VWT-destory方法銷毀局部變量。
所以,泛型在整個運行過程中的關鍵依賴于metadata

三、泛型函數(shù)分析

代碼如下:

//如果此時傳入的是一個函數(shù)呢?
func makeIncrement() -> (Int) -> Int{
    var runningTotal = 10
    return {
        runningTotal += $0
        return runningTotal
    }
}

func testGenric<T>(_ value: T){}

//m中存儲的是一個結構體:{i8*, swift type *}
let m = makeIncrement()
testGenric(m)

分析IR代碼:

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = alloca %swift.function, align 8
  %3 = bitcast i8** %1 to i8*
  ; s4main13makeIncrementS2icyF 調用makeIncrement函數(shù),返回一個結構體 {函數(shù)調用地址, 捕獲值的內存地址}
  %4 = call swiftcc { i8*, %swift.refcounted* } @"$s4main13makeIncrementS2icyF"()
     ; 閉包表達式的地址
  %5 = extractvalue { i8*, %swift.refcounted* } %4, 0
     ; 捕獲值的引用類型
  %6 = extractvalue { i8*, %swift.refcounted* } %4, 1
  
  ; 往m變量地址中存值
    ; 將 %5 存入 swift.function*結構體中(%swift.function = type { i8*, %swift.refcounted* })
    ; s4main1myS2icvp ==>  main.m : (Swift.Int) -> Swift.Int,即全局變量 m
  store i8* %5, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1myS2icvp", i32 0, i32 0), align 8
 
  ; 將值放入 f 這個變量中,并強轉為指針
  store %swift.refcounted* %6, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1myS2icvp", i32 0, i32 1), align 8
    ; 將%2 強轉為 i8*(即 void*)
  %7 = bitcast %swift.function* %2 to i8*
  call void @llvm.lifetime.start.p0i8(i64 16, i8* %7)
  
  ; 取出 function中 閉包表達式的地址
  %8 = load i8*, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1myS2icvp", i32 0, i32 0), align 8
  %9 = load %swift.refcounted*, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1myS2icvp", i32 0, i32 1), align 8
  
  ; 將返回的閉包表達式 當做一個參數(shù)傳入 方法,所以 retainCount+1
  %10 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %9) #2
  
  ; 創(chuàng)建了一個對象,存儲了 <{ %swift.refcounted, %swift.function }>*
  %11 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 32, i64 7) #2
  ; 將 %swift.refcounted* %11 強轉成了一個結構體類型
  %12 = bitcast %swift.refcounted* %11 to <{ %swift.refcounted, %swift.function }>*
  
  ; 取出 %swift.function (最終的結果就是往 <{ %swift.refcounted, %swift.function }> 的%swift.function 中存值 ==> 做了間接的轉換與傳遞) 
  %13 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %12, i32 0, i32 1
  ; 取出 <i8*, %swift.function>的首地址
  %.fn = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 0
  ; 將 i8* 放入 i8** %.fn 中(即創(chuàng)建的數(shù)據(jù)結構 <{ %swift.refcounted, %swift.function }> 的 %swift.function 中)
  store i8* %8, i8** %.fn, align 8
  %.data = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 1
  store %swift.refcounted* %9, %swift.refcounted** %.data, align 8
  %.fn1 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 0
  ; 將 %swift.refcounted 存入 %swift.function 中
  store i8* bitcast (void (%TSi*, %TSi*, %swift.refcounted*)* @"$sS2iIegyd_S2iIegnr_TRTA" to i8*), i8** %.fn1, align 8
  %.data2 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 1
  store %swift.refcounted* %11, %swift.refcounted** %.data2, align 8
  
  ; 將%2強轉成了 %swift.opaque* 類型,其中 %2 就是  %swift.function內存空間,即存儲的東西(函數(shù)地址 + 捕獲值地址)
  %14 = bitcast %swift.function* %2 to %swift.opaque*
  ; sS2icMD ==> demangling cache variable for type metadata for (Swift.Int) -> Swift.Int 即函數(shù)的metadata
  %15 = call %swift.type* @__swift_instantiateConcreteTypeFromMangledName({ i32, i32 }* @"$sS2icMD") #9
  
  ; 調用 testGenric 函數(shù)
  call swiftcc void @"$s4main10testGenricyyxlF"(%swift.opaque* noalias nocapture %14, %swift.type* %15)
  ......

仿寫泛型函數(shù)傳入函數(shù)時的底層結構:

//如果此時傳入的是一個函數(shù)呢?
struct HeapObject {
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refCount2: UInt32
}
struct FunctionData<T> {
    var ptr: UnsafeRawPointer
    var captureValue: UnsafePointer<T>
}
struct Box<T> {
    var refCounted: HeapObject
    var value: T
}
struct GenData<T> {
    var ref: HeapObject
    var function: FunctionData<T>
}

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

func testGenric<T>(_ value: T){
    //查看T的存儲
    let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)
    ptr.initialize(to: value)
    /*
     - 將 %13的值給了 %2即 %swift.function*
     %13 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %12, i32 0, i32 1
     
     - 調用方法 %14 -> %2
     %14 = bitcast %swift.function* %2 to %swift.opaque*
     call swiftcc void @"$s4main10testGenricyyxlF"(%swift.opaque* noalias nocapture %14, %swift.type* %15)
     */
    let ctx = ptr.withMemoryRebound(to: FunctionData<GenData<Box<Int>>>.self, capacity: 1) {
        $0.pointee.captureValue.pointee.function.captureValue
    }
    print(ctx.pointee.value)//捕獲的值是10
}

//m中存儲的是一個結構體:{i8*, swift type *}
let m = makeIncrement()
testGenric(m)

//打印結果:10

結論:當是一個泛型函數(shù)傳遞過程中,會做一層包裝,意味著并不會直接的將m中的函數(shù)值typetestGenric函數(shù),而是做了一層抽象,目的是解決不同類型在傳遞過程中的問題。

總結
  • 泛型主要用于解決代碼的抽象能力,以及提升代碼的復用性
  • 如果一個泛型遵循了某個協(xié)議,則在使用時,要求具體的類型也是必須遵循某個協(xié)議的;
  • 在定義協(xié)議時,可以使用關聯(lián)類型給協(xié)議中用到的類型起一個占位符名稱;
  • where語句主要用于表明泛型需要滿足的條件,即限制形式參數(shù)的要求
  1. 泛型類型使用VWT進行內存管理(即通過VWT區(qū)分不同類型),VWT由編譯器生成,其存儲了該類型的sizealignment以及針對該類型的基本內存操作
    1.1 當對泛型類型進行內存操作時(例如:內存拷貝)時,最終會調用對應泛型的VWT中的基本內存操作;
    1.2 泛型類型不同,其對應的VWT也不同;
    1.3當希望泛型指定類型時擁有特定功能,可以通過extension實現(xiàn)
  2. 對于泛型函數(shù)來說,有以下幾種情況:
    2.1 傳入的是一個值類型,例如,Integer:該類型的copymove操作會進行內存拷貝destory操作則不進行任何操作;
    2.1 傳入的是一個引用類型,如class:該類型的copy操作會對引用計數(shù)+1move操作會拷貝指針,而不會更新引用計數(shù)destory操作會對引用計數(shù)-1
  3. 如果泛型函數(shù)傳入的是一個函數(shù),在傳遞過程中,會做一層包裝,簡單來說,就是不會直接將函數(shù)的函數(shù)值+type給泛型函數(shù),而是做了一層抽象,主要是用于解決不同類型的傳遞問題
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容

  • Swift — 泛型(Generics) [TOC] 本文將介紹泛型的一些用法、關聯(lián)類型、where語句,以及對泛...
    just東東閱讀 1,337評論 0 3
  • 前言 本篇文章將分析Swift中最后一個重要的知識點 ?? 泛型,首先介紹一下概念,然后講解常用基礎的語法,最后重點...
    深圳_你要的昵稱閱讀 1,174評論 0 5
  • 前情提要 Swift的泛型側重于將類型作為一種變量或者占位符來使用。 為什么要用泛型呢,就是方便。 比如上一篇文章...
    Jacob6666閱讀 2,936評論 0 2
  • 為什么會有泛型 下面的 multiNumInt ( _ : _ :) 是個非泛型函數(shù),主要用于計算兩個數(shù)的乘積 m...
    晨曦的簡書閱讀 826評論 1 1
  • 泛型 泛型代碼讓你能根據(jù)自定義的需求,編寫出適用于任意類型的、靈活可復用的函數(shù)及類型。你可避免編寫重復的代碼,而是...
    xiaofu666閱讀 2,924評論 1 7