swift协议protocol中使用关联类型associatedtype和泛型的教程和注意事项

在协议中如何使用泛型

关联类型的关键字 :associatedtype

在协议中的格式如下:
associatedtype 类型变量,例如下面格式:

associatedtype E
associatedtype Base

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

protocol Stackble <Element>{   //这里编译会报错协议中不允许使用泛型参数,请使用关联类型代替泛型,Protocols do not allow generic parameters; use associated types instead
    mutating func push(_ element:Element)
    mutating func pop()->Element
    func top() ->Element
    func size() ->Int
}

在协议中使用关联类型代替泛型

  • 因为协议不允许使用泛型参数,想要协议使用泛型,请使用关联类型代替,正确的写法如下:
  • 只是把protocol<泛型类型> 改成了在{添加 associatedtype 泛型类型}
protocol Stackble {   //定义一个栈的协议
    associatedtype Element// 在协议中用来占位的类型是关联类型,声明一个关联类型Element
    mutating func push(_ element:Element)
    mutating func pop()->Element
    func top() ->Element
    func size() ->Int
}

在带泛型的class中,泛型类型填充关联类型

关联类型,是在协议中占位用的,class类中的泛型会代替关联类型
在class类中实现协议的代码如下: 可以看到class类中的泛型E,在重写push的时候,把类型换成了E,这样E就等于协议中的Element,这样,协议中其他方法 pop top ,这些事Element类型的,在class中都变成了泛型E


class Stack <E>: Stackble {
   
    var elements = [E]() //用关联类型
     func push(_ element:E){  //这里因为实现push 的时候给element输入了E,所以就相当于给Element 起了别名E
        //等于typealias E = Element
        
        elements.append(element)
    }
     func pop()->E{
        elements.removeLast()
    }
    func top() ->E{
        elements.last!
    }
    func size() ->Int{
        elements.count
    }
}

调用代码如下

var s1 = Stack<Int>()
s1.push(1)
s1.push(2)
s1.push(3)
print("s1.top()" ,s1.top())

运行打印结果:
s1.top() 3

泛型类型被基础类型替换

下面代码Stackble协议中的push方法的参数在协议中是Element类型,在class被重写的时候,push 的参数被重写成String,所以 Stackble协议中的其他方法,是Element类型类型的,都自动变成String


class StringStack:Stackble{
    var elements = [String]()
    func push(_ element:String){  //直接把关联类型用String指定
        
        elements.append(element)
    }
    func pop()->String{
        elements.removeLast()
    }
    func top() ->String{
        elements.last!
    }
    func size() ->Int{
        elements.count
    }
}

调用类的代码:

var ss1 = StringStack()
ss1.push("a1")
ss1.push("a2")
ss1.push("a3")
print("ss1.top()",ss1.top())
print("ss1.size()",ss1.size())

执行打印结果:
ss1.top() a3
ss1.size() 3

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

关联类型的协议不能作为返回值,函数形参

下面是正常的可以编译过的代码,协议中没有关联类型

 protocol Runnable {} //普通的协议,没有关联类型associatedtype
 class Person:Runnable{}
 class Car:Runnable{}
 func  get(_ type:Int) -> Runnable {//如果传入0,就创建Person对象,否则创建Car对象
 if(0 == type ){
 return Person()
 }
 return Car()
 }
 var r1 = get(0)//var r1: Runnable
 var r2 = get(1)//因为Runnable协议中没有关联类型,所有类型都明确,所以可以正常返回
 print("r1=",r1)
 print("r2=",r2)

执行打印结果,分别打印Person和Car:
r1= _7协议作为类型中有associatedtype的注意点.Person
r2= _7协议作为类型中有associatedtype的注意点.Car
代码中

下面是编译错误的代码

不能作为返回类型
protocol Runnable {
    associatedtype Speed
    var speed : Speed {get}
    
} //协议中有关联类型associatedtype,类型不确定
class Person:Runnable{
    var speed: Double = 0.0
}
class Car:Runnable{
    var speed: Double = 0.0
}

//协议Runnable里面有关联类型,类型不确定,所以不能当做定义函数的参数类型和返回值类型使用


func  get(_ type:Int) -> Runnable {//编译错误,协议有不确定关联类型,不能作为函数返回值
    if(0 == type ){
        return Person()
    }
    return Car()
}
不能作为函数形参
 fun get (run:Runnable ) {} //编译错误,协议Runnable里面有关联类型,类型不确定,所以不能当做定义函数的形参

解决让class遵循带关联类型的协议,并且能当做形参和返回值的方法

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

func get<T:Runnable>(_ type:Int)->T{ //让泛型类型T遵守协议,然后返回T
    if  0 == type{
        
        //        let result = Person() as T //编译错误,系统认为 Person() 创建的结果 强转成T 可能失败,所以要用as!强转,因为可能失败,可能返回nil,所以是可选类型,要用as !
        let result = Person() as! T
        return result
    }
    return Car() as! T
}

解决方法2:不明确类型

some让协议的关联类型变成透明的, 在协议前面标记上 some 后,返回值的类型对编译器就变成透明的了。在这个值使用的时候编译器可以根据返回值进行类型推断得到具体类型。如果不加some 编译报错,会认为返回的是个关联类型,是不确定的类型

@available(OSX 10.15.0, *)//要求系统超过10.15,编译提示自动添加
func get2(_ type:Int )-> some Runnable{ //some让协议的关联类型变成透明的, 在协议前面标记上 some 后,返回值的类型对编译器就变成透明的了。在这个值使用的时候编译器可以根据返回值进行类型推断得到具体类型。如果不加some 编译报错,会认为返回的是个关联类型,是不确定的类型
    return Car() //some只能返回一种类型
}

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

 func get3(_ type:Int )-> some Runnable{ //编译错误,some限制的类型不能返回2种类型
 if  0 == type{
 return Person()
 }
 return Car() //some只能返回一种类型
 }
Swift中,协议是两个非常有用的特性,它们可以帮助我们编写更加灵活可重用的代码。 ### 是指在编写代码时,使用占位符来表示数据类型。这样我们可以编写可以处理任何类型数据的代码,而不用针对不同的数据类型编写不同的代码。 例如,我们可以编写一个函数来交换两个变量的值: ```swift func swap<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp } var x = 1 var y = 2 swap(&x, &y) print("x = \(x), y = \(y)") // 输出 x = 2, y = 1 ``` 在这个例子中,我们使用了一个名为`T`的占位符来表示数据类型。这个函数可以处理任何类型的变量,只要它们可以被互相交换。 ### 协议 协议是一种规范,用于描述类、结构体枚举应该具有的属性方法。协议可以被任意类型遵循,并且可以作为参数、返回值或约束。 例如,我们可以定义一个协议来描述可以被排序的类型: ```swift protocol Sortable { associatedtype Element func sort() -> [Element] } extension Array: Sortable where Element: Comparable { func sort() -> [Element] { return self.sorted() } } ``` 在这个例子中,我们定义了一个名为`Sortable`的协议,它有一个关联类型`Element`一个`sort`方法。我们还扩展了数组类型,使它可以遵循`Sortable`协议,并实现`sort`方法。 现在我们可以使用`Sortable`协议来对任何可以被排序的类型进行排序: ```swift func sort<T: Sortable>(_ value: T) -> [T.Element] { return value.sort() } let numbers = [3, 1, 4, 1, 5, 9, 2] let sortedNumbers = sort(numbers) print(sortedNumbers) // 输出 [1, 1, 2, 3, 4, 5, 9] ``` 在这个例子中,我们定义了一个函数`sort`,它接受任何遵循`Sortable`协议类型。我们可以将数组`numbers`传递给`sort`函数,因为它可以被排序,并且已经遵循了`Sortable`协议。 这里只是简单介绍了Swift协议使用,它们还有很多高级用法细节。如果你想深入学习它们,可以查看Swift官方文档或其他相关教程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值