泛型
Swift泛型(generics) 让我们写出的类型和函数可以使用对于我们或编译器都未知的类型。 很多内建类型(包括可空类型、数组和字典)都是用泛型实现的,比如数组和一些集合就是用泛型方式来实现的。
一种运行时进行类型检查的技术,效率高但是不安全。在swift中泛型可用于结构体、类以及函数和方法
定义可空类型的泛型变量
let x: Optional<Int> = 3
print(x) //~~ 3
上述代码实际上用到了系统提供的协议,具体的在后续详细讲解,这里只了解其用法就可以了
enum Optional<Wrapped> {
case None
case Some(Wrapped)
}
定义泛型类型
语法:
class/struct Name<Type>
,上述尖括号中的Type是一个占位符,也可换成其它名称,比如T,在实例化时会换成实际的值。
比如一个简单的堆栈实现
//Element是一个占位符,也可换成其它名称
struct Stack<T>: Sequence {
var items = [T]()
mutating func push(_ newItem: T) {
items.append(newItem)
}
mutating func pop() -> T? {
guard !items.isEmpty else {
return nil
}
return items.removeLast()
}
}
//~~~使用,在代码运行时,泛型T会换成Int
var stack = Stack<Int>()
定义泛型函数和方法
函数和类的方法的返回值和参数也可以用泛型来代码,比如下拉代码声明了一个泛型函数。
func myMap<T,U> ( _ items:[T], _ f:(T)->(U) ) -> [U]{
var result = [U]()
for item in items{
result.append( f(item) )
}
return result
}
方法测试
let string = ["one", "two", "three"]
let stringLen = myMap(string){$0.count} //第一个cod为一个函数
print(stringLen) //~~ [3,3,5]
给泛型占位符设置约束条件
泛型的比较,必须要把泛型标识符声明为系统提供的Equatable类型。
func checkIfEqual<T: Equatable>(_ first: T, _ second: T) -> Bool {
return first == second
}
print(checkIfEqual(1, 1)) //~~true
print(checkIfEqual("a string", "a string")) //~~true
print(checkIfEqual("a string", "a different string")) //~~false
多个约束符的例子,下例表示用CustomStringConvertible保证了first和second都有返回字符串的属性description。
func checkIfDescriptionsMatch<T: CustomStringConvertible, U: CustomStringConvertible>(
_ first: T, _ second: U) -> Bool {
return first.description == second.description
}
print(checkIfDescriptionsMatch(Int(1), UInt(1)))
print(checkIfDescriptionsMatch(1, 1.0))
print(checkIfDescriptionsMatch(Float(1.0), Double(1.0)))
泛型与协议
协议是不可以直接使用泛型的,如果想在协议中使用泛型,可以使用一个叫“关联类型”的特性。用到关键字 associatedtype
来修饰协议属性,比如系统提供的IteratorProtocol协议就是如下定义的:
protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
上述associatedtype Element表示符合这个协议的类型必须提供具体类型做为Element类型。符合这个协议的类型应该在其定义为内部为Element提供typealias定义,那么就可以按如下方式使用了:
//用 StackIterator 把Stack封装起来。
struct StackIterator<T>: IteratorProtocol {
typealias Element = T
var stack: Stack<T>
mutating func next() -> Element? {
return stack.pop()
}
}
这段代码可以借助Swift类型推断功能,简写为如下形式:
struct StackIterator<T>: IteratorProtocol {
var stack: Stack<T>
mutating func next() -> T? {
return stack.pop()
}
}
使用
var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
var myStackIterator = StackIterator(stack: myStack)
while let value = myStackIterator.next() {
print("got \(value)")
}
where子语句
用占位类型s把pushAll(_:)变成泛型方法,它是符合Sequence协议的类型。S的约束保证我们可以用for-in语法循环遍历之。不过,这还不够。为了把从sequence中取出的数据项推入栈,需要确保从序列类型中来的数据项类型和栈元素的类型匹配。也就是说,还需要一个约束让S所产生元素的类型是Element。
//where语句相当于一个过滤器
mutating func pushAll<S: Sequence>(_ sequence: S)
where S.Iterator.Element == Element {
for item in sequence {
self.push(item)
}
}
附:Stack 示例实现
struct Stack<Element>: Sequence {
var items = [Element]()
mutating func push(_ newItem: Element) {
items.append(newItem)
}
mutating func pop() -> Element? {
guard !items.isEmpty else {
return nil
}
return items.removeLast()
}
func map<U>(_ f: (Element) -> U) -> Stack<U> {
var mappedItems = [U]()
for item in items {
mappedItems.append(f(item))
}
return Stack<U>(items: mappedItems)
}
// Sequence 协议的方法
func makeIterator() -> StackIterator<Element> {
return StackIterator(stack: self)
}
mutating func pushAll<S: Sequence>(_ sequence: S) where S.Iterator.Element == Element {
for item in sequence {
self.push(item)
}
}
}
类型操作
值的比较
类型的比较在很多场景下都有需求,在Swift中可通过实现Equatable和Comparable这两个协议来实现。
实现Equatable协议
struct Point: Equatable {
let x: Int
let y: Int
static func == (lhs: Point, rhs: Point) -> Bool {
return (lhs.x == rhs.x) && (lhs.y == rhs.y)
}
}
let a = Point(x: 3, y: 4)
let b = Point(x: 3, y: 4)
let abEqual = (a == b) //~~ true
let noAbEqual = (a != b) //~~ false
上述代码中==(中缀运算符)被声明为了static方法,事实上==是定义在全局范围内的。
实现Comparable协议
Comparable会提供更多的功能,因为Comparable继承了Equatable。
//自定义的Point结构体实现Comparable
struct Point: Comparable { //因为继承的原因,所以这块不需要写成Equatable,Comparable
let x: Int
let y: Int
static func ==(lhs: Point, rhs: Point) -> Bool {
return (lhs.x == rhs.x) && (lhs.y == rhs.y)
}
static func <(lhs: Point, rhs: Point) -> Bool {
return (lhs.x < rhs.x) && (lhs.y < rhs.y)
}
}
let a = Point(x: 3, y: 4)
let b = Point(x: 3, y: 4)
let abEqual = (a == b) //true
let abNotEqual = (a != b) //false
let c = Point(x: 2, y: 6) //false
let d = Point(x: 3, y: 7) //false
let cdEqual = (c == b) //false
let cLessThanD = (c < d) //true
let cLessThanEqualD = (c <= d) //true
let cGreaterThanD = (c > d) //false
let cGreaterThanEqualD = (c >= d) //false
自定义运算符
Swift允许开发者创建自定义运算符。这个特性意味着我们可以创建自己的运算符来表示两个Person的实例结婚了。自定义运算符不太建议使用,因为它也只限于数学运算范围内,正常情况下使用系统提供的就够了。
定义自定义类
class Person: Equatable {
var name: String
var age: Int
weak var spouse: Person?
init(name: String, age: Int) {
self.name = name
self.age = age
}
func marry(_ spouse: Person) {
self.spouse = spouse
spouse.spouse = self
}
}
添加自定义运算符
在Person类声明的外面添加以下代码,以添加自定义运算符。
//声明一个新运算符
infix operator +++
func +++(lhs: Person, rhs: Person) {
lhs.spouse = rhs
rhs.spouse = lhs
}
添加自定义运算符到默认组
precedencegroup Marriage {
associativity: none //这是一个运算优先级定义
}
如果没有上述代码,则因为swift内部对新添加的运算符默认添加到swift内部默认的组为DefaultPrecedence。
使用自定义运算符
let drew = Person(name: "Drew", age: 33)
let matt = Person(name: "Matt", age: 32)