前言
本篇文章將分析Swift中最后一個(gè)重要的知識(shí)點(diǎn) ?? 泛型,首先介紹一下概念,然后講解常用基礎(chǔ)的語法,最后重點(diǎn)分析下泛型函數(shù),主要是從IR代碼層面分析下泛型函數(shù)的調(diào)用流程。
一、泛型的概念
首先說一下泛型的概念 ??
- 泛型代碼能根據(jù)所定義的要求寫出可以用于
任何類型的靈活的、可復(fù)用的函數(shù)。可以編寫出可復(fù)用、意圖表達(dá)清晰、抽象的代碼。 - 泛型是
Swift最強(qiáng)大的特性之一,很多Swift標(biāo)準(zhǔn)庫(kù)是基于泛型代碼構(gòu)建的。
例如,Swift的Array和Dictionary類型都是泛型集合??梢詣?chuàng)建一個(gè)容納Int 值的數(shù)組,或者容納String 值的數(shù)組,甚至容納任何 Swift 可以創(chuàng)建的其他類型的數(shù)組。同樣,可以創(chuàng)建一個(gè)存儲(chǔ)任何指定類型值的字典,而且類型沒有限制。 - 泛型所解決的問題:
代碼的復(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.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(對(duì)齊方式)、stride(步長(zhǎng)),大致結(jié)構(gòu)圖??

所以metadata中都存放了VWT來管理類型的值。比如Int、String、Class的復(fù)制銷毀、創(chuàng)建以及是否需要引用計(jì)數(shù)。
再回過頭來看看上面示例的IR代碼,其實(shí)執(zhí)行的流程大致如下??
- 詢問
metadata中VWT:size,stride分配內(nèi)存空間 - 初始化
temp - 調(diào)用
VWT-copy方法拷貝值到temp - 返回
temp - 調(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 initializeWithCopy 和initializeWithTake。再回過頭來看??

所以??
對(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)均已覆蓋完畢,感謝大家的支持!
