Swift 泛型
Swift 提供了泛型让你写出灵活且可重用的函数和类型。
Swift 标准库是通过泛型代码构建出来的。
Swift 的数组和字典类型都是泛型集。
你可以创建一个Int数组,也可创建一个String数组,或者甚至于可以是任何其他 Swift 的类型数据数组。
泛型使用了占位类型名(在这里用字母 T 来表示)来代替实际类型名(例如 Int、String 或 Double)。
func swapTwoValuse<T>(_ a: inout T, _ b: inout T){
let temp = a
a = b
b = temp
}
var stringA = "A"
var stringB = "B"
swapTwoValuse(&stringA, &stringB)
print("stringA:\(stringA) stringB:\(stringB)")
运行结果:
stringA:B stringB:A
swapTwoValues 后面跟着占位类型名(T),并用尖括号括起来()。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(:? 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 T 的实际类型。
泛型类型
Swift 允许你定义你自己的泛型类型。
自定义类、结构体和枚举作用于任何类型,如同 Array 和 Dictionary 的用法。
接下来我们来编写一个名为 Stack (栈)的泛型集合类型,栈只允许在集合的末端添加新的元素(称之为入栈),且也只能从末端移除元素(称之为出栈)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7PDKysma-1571993702292)(evernotecid://9410CF3A-D2D2-43A9-92EF-E402B57DB340/appyinxiangcom/7214276/ENResource/p192)]
图片中从左到右解析如下:
- 三个值在栈中。
- 第四个值被压入到栈的顶部。
- 现在有四个值在栈中,最近入栈的那个值在顶部。
- 栈中最顶部的那个值被移除,或称之为出栈。
- 移除掉一个值后,现在栈又只有三个值了。
Int 型的栈
struct IntStack {
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
这个结构体在栈中使用一个名为 items 的 Array 属性来存储值。Stack 提供了两个方法:push(_? 和 pop(),用来向栈中压入值以及从栈中移除值。这些方法被标记为 mutating,因为它们需要修改结构体的 items 数组。
上面的 IntStack 结构体只能用于 Int 类型。不过,可以定义一个泛型 Stack 结构体,从而能够处理任意类型的值。
扩展泛型类型
当你扩展一个泛型类型的时候(使用 extension 关键字),你并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
泛型的栈
struct Stack<element> {
var items = [element]()
mutating func push(_ A: element){
items.append(A)
}
mutating func pop() -> element{
return items.removeLast()
}
}
//扩展泛型
extension Stack {
var topItem: element?{
return items.isEmpty ? nil : items[items.count-1]
}
}
var stacOfStrings = Stack<String>()
stacOfStrings.push("first")
stacOfStrings.push("second")
print("字符串元素入栈:\(stacOfStrings.items)")
let outItem = stacOfStrings.pop()
print("出栈元素:\(outItem)")
var stackOfInts = Stack<Int>()
stackOfInts.push(1)
stackOfInts.push(2)
print("整数元素入栈:\(stackOfInts.items)")
let topItem = stackOfInts.topItem
print("Top元素:\(topItem!)")
输出结果:
字符串元素入栈:["first", "second"]
出栈元素:second
整数元素入栈:[1, 2]
Top元素:2
类型约束
类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。
类型约束语法
你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}
关联类
Swift 中使用 associatedtype 关键字来设置关联类型实例。
下面例子定义了一个 Container 协议,该协议定义了一个关联类型 ItemType。
Container 协议只指定了三个任何遵从 Container 协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的
protocol Container {
// associatedtype 关键字来设置关联类型实例
associatedtype ItemType
// 添加一个新元素到容器里
mutating func append(_ item: ItemType)
// 获取容器中元素的数
var count: Int { get }
// 通过索引值类型为 Int 的下标检索到容器中的每一个元素
subscript (index: Int) -> ItemType { get }
}
struct Stack<element>: Container {
var items = [element]()
mutating func push(_ A: element){
items.append(A)
}
mutating func pop() -> element{
return items.removeLast()
}
//protocol
mutating func append(_ item: element) {
self.push(item)
}
var count: Int{
return items.count
}
subscript(index: Int) -> element {
return items[index]
}
}
var stackOfStrs = Stack<String>()
stackOfStrs.append("AAA")
stackOfStrs.append("BBB")
print(stackOfStrs.count, stackOfStrs[0])
运行结果
2 AAA
Where 语句
类型约束能够确保类型符合泛型函数或类的定义约束。
你可以在参数列表中通过where语句定义参数的约束。
你可以写一个where语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。
实例
下面的例子定义了一个名为allItemsMatch的泛型函数,用来检查两个Container实例是否包含相同顺序的相同元素。
如果所有的元素能够匹配,那么返回 true,反之则返回 false。
protocol Container {
// associatedtype 关键字来设置关联类型实例
associatedtype ItemType
// 添加一个新元素到容器里
mutating func append(_ item: ItemType)
// 获取容器中元素的数
var count: Int { get }
// 通过索引值类型为 Int 的下标检索到容器中的每一个元素
subscript (index: Int) -> ItemType { get }
}
struct Stack<element>: Container {
var items = [element]()
mutating func push(_ A: element){
items.append(A)
}
mutating func pop() -> element{
return items.removeLast()
}
//protocol
mutating func append(_ item: element) {
self.push(item)
}
var count: Int{
return items.count
}
subscript(index: Int) -> element {
return items[index]
}
}
// 扩展,将 Array 当作 Container 来使用,可以让数组传到方法里
extension Array: Container {}
//方法
func allItemsMatch<C1: Container, C2: Container>(_ firstContainer: C1, _ AnotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType,C1.ItemType: Equatable {
//容器容量不等
if firstContainer.count != AnotherContainer.count {
return false
}
//容器内Item不等
for index in 0..<firstContainer.count {
if firstContainer[index] != AnotherContainer[index] {
return false
}
}
return true
}
let arrayA = ["baidu", "ali", "tencent"]
let arrayB = ["baidu", "ali", "tencent"]
let result = allItemsMatch(arrayA, arrayB)
if result {
print("所有元素匹配相等")
}else{
print("匹配不成功")
}
执行结果:
所有元素匹配相等
Swift 访问控制
访问控制可以限定其他源文件或模块中代码对你代码的访问级别。
你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、函数、初始化方法、基本类型、下标索引等设置访问级别。
协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。
访问控制基于模块与源文件。
模块指的是以独立单元构建和发布的 Framework 或 Application。在 Swift 中的一个模块可以使用 import 关键字引入另外一个模块。
源文件是单个源码文件,它通常属于一个模块, 源文件可以包含多个类和函数 的定义。
Swift 为代码中的实体提供了四种不同的访问级别:public、internal、fileprivate、private。
访问级别 | 定义 |
---|---|
public | 可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。 |
internal | 可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。 |
fileprivate | 文件内私有,只能在当前源文件中使用。 |
private | 只能在类中访问,离开了这个类或者结构体的作用域外面就无法访问。 |
public 为最高级访问级别,private 为最低级访问级别。
除非有特殊的说明,否则实体都使用默认的访问级别 internal。
函数类型访问权限
函数的访问级别需要根据该函数的参数类型和返回类型的访问级别得出。
函数中其中一个类 SomeInternalClass 的访问级别是 internal,另一个 SomePrivateClass 的访问级别是 private。所以根据元组访问级别的原则,该元组的访问级别是 private(元组的访问级别与元组中访问级别最低的类型一致)。
因为该函数返回类型的访问级别是 private,所以你必须使用 private 修饰符,明确的声明该函数:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 函数实现
}
将该函数申明为 public 或 internal,或者使用默认的访问级别 internal 都是错误的,因为如果这样你就无法访问 private 级别的返回值。
枚举类型访问权限
枚举中成员的访问级别继承自该枚举,你不能为枚举中的成员单独申明不同的访问级别。
实例
比如下面的例子,枚举 Student 被明确的申明为 public 级别,那么它的成员 Name,Mark 的访问级别同样也是 public:
实例
public enum Student {
case Name(String)
case Mark(Int,Int,Int)
}
子类访问权限
子类的访问级别不得高于父类的访问级别。比如说,父类的访问级别是internal,子类的访问级别就不能申明为public。
public class SuperClass {
fileprivate func show() {
print("超类")
}
}
// 访问级别不能高于超类 internal < public
internal class SubClass: SuperClass {
override internal func show() {
print("子类")
}
}
常量、变量、属性、下标访问权限
常量、变量、属性不能拥有比它们的类型更高的访问级别。
比如说,你定义一个public级别的属性,但是它的类型是private级别的,这是编译器所不允许的。
同样,下标也不能拥有比索引类型或返回类型更高的访问级别。
如果常量、变量、属性、下标索引的定义类型是private级别的,那么它们必须要明确的申明访问级别为private:
private var privateInstance = SomePrivateClass()
Getter 和 Setter访问权限
常量、变量、属性、下标索引的Getters和Setters的访问级别继承自它们所属成员的访问级别。
Setter的访问级别可以低于对应的Getter的访问级别,这样就可以控制变量、属性或下标索引的读写权限。
构造器和默认构造器访问权限
初始化
我们可以给自定义的初始化方法申明访问级别,但是要不高于它所属类的访问级别。但必要构造器例外,它的访问级别必须和所属类的访问级别相同。
如同函数或方法参数,初始化方法参数的访问级别也不能低于初始化方法的访问级别。
默认初始化方法
Swift为结构体、类都提供了一个默认的无参初始化方法,用于给它们的所有属性提供赋值操作,但不会给出具体值。
默认初始化方法的访问级别与所属类型的访问级别相同。
实例
在每个子类的 init() 方法前使用 required 关键字声明访问权限。
class classA {
required init() {
var a = 10
print(a)
}
}
class classB: classA {
required init() {
var b = 30
print(b)
}
}
协议访问权限
如果想为一个协议明确的申明访问级别,那么需要注意一点,就是你要确保该协议只在你申明的访问级别作用域中使用。
如果你定义了一个public访问级别的协议,那么实现该协议提供的必要函数也会是public的访问级别。这一点不同于其他类型,比如,public访问级别的其他类型,他们成员的访问级别为internal。
实例
public protocol TcpProtocol {
init(no1: Int)
}
public class MainClass {
var no1: Int // local storage
init(no1: Int) {
self.no1 = no1 // initialization
}
}
class SubClass: MainClass, TcpProtocol {
var no2: Int
init(no1: Int, no2 : Int) {
self.no2 = no2
super.init(no1:no1)
}
// Requires only one parameter for convenient method
required override convenience init(no1: Int) {
self.init(no1:no1, no2:0)
}
}
扩展访问权限
你可以在条件允许的情况下对类、结构体、枚举进行扩展。扩展成员应该具有和原始类成员一致的访问级别。比如你扩展了一个公共类型,那么你新加的成员应该具有和原始成员一样的默认的internal访问级别。
或者,你可以明确申明扩展的访问级别(比如使用private extension)给该扩展内所有成员申明一个新的默认访问级别。这个新的默认访问级别仍然可以被单独成员所申明的访问级别所覆盖。
泛型访问权限
泛型类型或泛型函数的访问级别取泛型类型、函数本身、泛型类型参数三者中的最低访问级别。
实例
public struct TOS<T> {
var items = [T]()
private mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
}
类型别名
任何你定义的类型别名都会被当作不同的类型,以便于进行访问控制。一个类型别名的访问级别不可高于原类型的访问级别。
比如说,一个private级别的类型别名可以设定给一个public、internal、private的类型,但是一个public级别的类型别名只能设定给一个public级别的类型,不能设定给internal或private 级别的类型。
注意:这条规则也适用于为满足协议一致性而给相关类型命名别名的情况。