Swift泛型知识点梳理

87 阅读5分钟

Swift泛型

泛型定义

泛型是为Swift编程灵活性的一种语法,在函数、枚举、结构体、类中都得到充分的应用,它的引入可以起到占位符的作用,当类型暂时不确定的,只有等到调用函数时才能确定具体类型的时候可以引入泛型

如何在Swift中定义泛型函数

尖括号+泛型名称:<T>

如何在Swift中定义泛型类型

struct Stack<Element> {
    var items = [Element]()
    
    mutating func push(_ item: Element){
        items.append(item)
    }
    
    mutating func pop() -> Element {
        return items.removeLast()
    }
}
//扩展一个泛型类型,不需要在扩展的定义中再定义泛型类型
extension Stack{
  var topItem: Element? {
    return items.isEmpty? nil : items[items.count -1]
  }
}

Swift泛型中的类型约束

swapTwoValues(:,:)函数和Stack类型可以用于任意类型。但是,又是在用于泛型函数的类型和泛型类型上,强制其遵循特定的类型约束很有用。类型约束指出一个类型形式参数必须继承自特定类,或者遵循一个特定的协议、组合协议。

例如Dictionary类型在可以用于字典的类型上设置一个限制,字典键的类型必须是可哈希的,它必须提供一种使其可以唯一表示的方法。没有这个要求,Dictionary不能区分插入还是替换一个指定键的值,也不能在字典中查找已经给定的键的值。

func someFunction<T: SomeClass,U: SomeProtocol>(someT: T, someU: U){
	//function body goes here
}
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int?{
    for(index ,value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
        
}
//测试
let names= ["zhangsan","lisi","wangwu"]
let nums = [1,3,5]
print(findIndex(of: "lisi", in:names))
print(findIndex(of: 3, in:nums))

关联类型

定义一个协议时,有时在协议定义里声明一个或多个关联类型是很有用,关联类型给协议中用到的类型一个占位符名称。直到采纳协议时,才指定用于该关联类型的具体类型。

protocol Container{
    associatedtype ItemType
    mutating func append(_ item: ItemType)
    var count: Int{get}
    subscript(i: Int) -> ItemType { get}
}

协议中使用泛型不能像在class类中那样使用:

//编译错误,需要使用associatedtype代替
protocol Container<ItemType> {
    mutating func push(_ element:ItemType)
    mutating func pop() -> ItemType
    func top() -> ItemType
    
}

struct和class在实现协议的代码中,可以对关联类型的类进行重写,比如下面的Element等同于ItemType

struct Stack<Element>: Container {
    var items = [Element]()
    mutating func append(_ item: Element) {
        items.append(item)
    }
    
    subscript(i: Int) -> Element {
        return items[i]
    }
    
    var count: Int {
        return items.count
    }
    
    mutating func push(_ item: Element){
        items.append(item)
    }
    
    mutating func pop() -> Element {
        return items.removeLast()
    }
    
}
//测试
var sta	= Stack<Int>()
sta.push(1)
sta.push(2)
sta.pop()

协议中关联类型的注意事项

关联类型的协议不能作为返回值,函数形参,而没有关联类型的协议可以

//正常编译
protocol Runnable{}

class People: Runnable{}
class Car: Runnable{}

func getRunnable(type: Int) -> Runnable{
    if type == 0{
        return People()
    }
    return Car()
}

var va1 = getRunnable(type: 0)
var va2 = getRunnable(type: 2)
//------------------------------------------------------------------------------------------
//编译错误:Use of protocol 'Runnable' as a type must be written 'any Runnable'
protocol Runnable{
    associatedtype Speed
    var speed: Speed{get}
}

class People: Runnable{
    var speed: Double = 0.0
}
class Car: Runnable{
    var speed: Double = 0.0
}
//返回值:❌
func getRunnable(type: Int) -> Runnable{
    if type == 0{
        return People()
    }
    return Car()
}
//形参:❌
func nameOfRunnable(_ run: Runnable){}


解决方法1:让泛型遵循协议,然后让泛型当做形参返回值

func getRunnable<T: Runnable>(type: Int) -> T{
    if type == 0{
        return People() as! T
    }
    return Car() as! T
}

解决方法2:不明确类型 使用关键字some

//some 让协议关联类型变成透明的,在协议前面标记 some 后,返回值的类型对编译器变成透明的,
//在这个值使用的时候编译器可以根据返回值进行类型推断得到具体的类型。
//如果不加some 编译报错,会认为返回的是个关联类型,是不确定的类型。
func get2(_ type:Int) -> some Runnable {
    return Car()
}

下面的代码是错误的,因为some不能返回2种类型

func get3(_ type:Int) -> some Runnable {
    if 0 == type{
    	return People()
    }
    return Car()
}

类型擦除

  1. 关联协议:带有关联类型的协议(PATs, Protocols with Associated Types)。
  2. 类型擦除:利用一个具体实现的通用泛型类(参看系统库的AnySequence),去包装具体实现了该泛型协议的类。用以解决不能直接使用泛型协议进行变量定义的问题。
typealias NetworkBody<T> = (Result<T, Error>) -> ()
protocol NetworkFetch{
    associatedtype DataType
    func fetch(_ completion:NetworkBody<DataType>?)
}

struct UserInfo{
    var name:String?
    var age:Int?
}
struct UserNetworkFetch: NetworkFetch{
    typealias DataType = UserInfo
    
    func fetch(_ completion: NetworkBody<UserInfo>?) {
        //异步请求成功后
        completion?(.success(UserInfo(name: "Kevin",age: 15)))
    }
}
struct AnyNetworkFetch<T>: NetworkFetch{
    private let _fetch: (NetworkBody<T>?)->()
    init<U: NetworkFetch>(_ u: U) where U.DataType == T {
        _fetch = u.fetch
    }
    func fetch(_ completion: NetworkBody<T>?) {
        _fetch(completion)
    }
    typealias DataType = T
}
class SomeViewController: UIViewController{
    var map: Dictionary<String, Any>?
    let networkFetch: AnyNetworkFetch<UserInfo> = AnyNetworkFetch(UserNetworkFetch())
    
    override func viewDidLoad() {
        super.viewDidLoad()
        loadData()
    }
    
    func loadData(){
        self.networkFetch.fetch { result in
            switch result {
            case .success(let userInfo):
                print("success:\(userInfo)")
            case .failure(let error):
                print("failure!!!!")
            }
        }
    }
}

如何为泛型定义要求:where子句

extension Container{
	func allItemsMatch<C1: Container, C2: Container>(container: C1, anotherContainer: C2) -> Bool 
    where C1.ItemType == C2.ItemType, C1.ItemType : Equatable {
        if container.count != anotherContainer.count {
            return false
        }
        for i in 0 ..< container.count {
            if container[i] != anotherContainer[i]{
                return false
            }
        }
        return true
    }
}

泛型下标

extension Container{
    subscript<Indices: Sequence>(indexs: Indices) -> [ItemType]  
    where Indices.Iterator.Element == Int{
        var result = [ItemType]()
        for index in indexs {
            result.append(self[index])
        }
        return result
    }
}

泛型编程思想总结

面向过程的编程,可以将常用代码封装在一个函数中,然后通过函数调用来达到代码重用的目的。 面向对象的编程,则可以通过类的继承来实现代码重用。

泛型编程,编写以类型作为参数的一个模版函数,调用的时候再将参数实例化为具体的数据类型。(面向算法的多态技术)

是一类通用的参数化算法,他们对各种数据类型和数据结构都能以相同的方式进行工作,实现源代码级的软件重用。