泛型
是Swift
最強大的特性之一,oc轉Swift的需要重點學習一下。
① 泛型代碼能根據(jù)所定義的要求寫出可以用于任何類型的靈活的、可復用的函數(shù)。可以編寫出可復用、意圖表達清晰、抽象的代碼。
② 泛型是Swift最強大的特性之一,很多Swift標準庫是基于泛型代碼構建的。如,Swift 的Array
和Dictionary
類型都是泛型集合;你也可以創(chuàng)建一個容納Int
值的數(shù)組,或者容納String
值的數(shù)組,甚至容納任何 Swift 可以創(chuàng)建的其他類型的數(shù)組。同樣,可以創(chuàng)建一個存儲任何指定類型值的字典,而且類型沒有限制。
③ 泛型所解決的問題:代碼的復用性和抽象能力
。比如,交換兩個值,這里的值可以是Int
、Double
、String
。
//經(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(步長)
、destory
、copy(函數(shù))
。
2.1 VWT
看下VWT
的源碼(在Metadata.h
中TargetValueWitnessTable
):
/// 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(步長)
,大致結構圖:
metadata
中存放了VWT
來管理類型的值。
回過頭,示例的IR代碼執(zhí)行的流程大致如下:詢問metadata
中VWT: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ù)值
、type
給testGenric函數(shù)
,而是做了一層抽象
,目的是解決不同類型在傳遞過程中的問題。
總結
- 泛型主要用于解決代碼的
抽象能力
,以及提升代碼的復用性
; - 如果一個泛型
遵循了某個協(xié)議
,則在使用時,要求具體的類型也是必須遵循某個協(xié)議的; - 在定義協(xié)議時,可以使用
關聯(lián)類型
給協(xié)議中用到的類型起一個占位符名稱; -
where語句
主要用于表明泛型需要滿足的條件,即限制形式參數(shù)的要求
。
- 泛型類型使用
VWT
進行內存管理(即通過VWT區(qū)分不同類型
),VWT
由編譯器生成,其存儲了該類型的size
、alignment
以及針對該類型的基本內存操作
;
1.1 當對泛型類型進行內存操作時(例如:內存拷貝)時,最終會調用對應泛型的VWT
中的基本內存操作;
1.2 泛型類型不同,其對應的VWT也不同;
1.3當希望泛型指定類型時擁有特定功能,可以通過extension實現(xiàn)
。 - 對于
泛型函數(shù)
來說,有以下幾種情況:
2.1 傳入的是一個值類型
,例如,Integer
:該類型的copy
和move
操作會進行內存拷貝
;destory
操作則不進行任何操作;
2.1 傳入的是一個引用類型
,如class
:該類型的copy
操作會對引用計數(shù)+1
;move
操作會拷貝指針,而不會更新引用計數(shù)
;destory
操作會對引用計數(shù)-1
。 - 如果泛型函數(shù)傳入的是
一個函數(shù)
,在傳遞過程中,會做一層包裝,簡單來說,就是不會直接將函數(shù)的函數(shù)值+type
給泛型函數(shù),而是做了一層抽象,主要是用于解決不同類型的傳遞問題
。