点赞评论,感觉有用的朋友可以关注笔者公众号 iOS 成长指北,持续更新
从语义上说,Swift 中 Typealias 就是现有类的别名。当我们谈及 Swift 中的语言特性时,Typealias 往往不够起眼。
提及 Typealias ,一个简单的作用就是给现有类添加别名,为了减轻阅读和维护负担,我们给一些特定用处的实例使用别名作为类型:
import UIKit
typealias Location = CGPoint
typealias Distance = Double
诚然,这有点作用,但仅仅只为了添加一个别名,对你来说可能没那么有用!
今天我们详细了解一下 Swift 中的 Typealias 。通过巧妙地的方法,在你的代码库中会非常有用。
希望今天的内容能够足够干。
学习目标
通过完成本文的学习,我们主要解决以下几个问题
- 什么是 Typealias?Typealias 是一种新类型吗?
- 从实践出发,我们能用 Typealias 解决什么问题。
Typealias
Typealias 顾名思义是特定类型的别名。使用 typealias 关键字像使用普通的赋值语句一样,可以将某个已经存在的类型 赋值为新的名字。
typealias Dollar = Double
当我们定义某个产品的价格时,我们定义价格的类型为 Dollar
而不是 Double
。当我们需要定义一个价格时,我们使用 Dollar
。
let price: Dollar = 0.0
Typealias 是创建自定义类或子类的简单替代方法。
Typealias 是一种新类型吗?
那么 Typealias 是一种新类型吗?不,基本上,这只是一个已存在类型的命名别名。我们的 Dollar
别名只是一个具有不同名称的 Double
,这与Double
的使用方式完全相同。
反之亦然。如果要为 Typealias 创建扩展的话,基本上是在为其底层类型创建扩展。
Swift 中的我们常用的很多类就是 Typealias 了。例如:
#if CLR || JAVA
public typealias Int = Int64
public typealias UInt = UInt64
#elseif ISLAND
#if CPU64
public typealias Int = Int64
public typealias UInt = UInt64
#elseif CPU32
public typealias Int = Int32
public typealias UInt = UInt32
#else
#hint Unexpected bitness
public typealias Int = Int64
public typealias UInt = UInt64
#endif
#elseif COCOA
public typealias Int = NSInteger
public typealias UInt = NSUInteger
#endif
...
public typealias Any = protocol<> //Dynamic;
public typealias AnyObject = protocol<> //Dynamic;
#if CLR
public typealias AnyClass = System.`Type`
#elseif COCOA
public typealias AnyClass = rtl.Class
#elseif JAVA
public typealias AnyClass = java.lang.Class
#endif
这是不是很像 C 语言中的 typedef 关键字。
Typealias 实践指南
下面我将介绍几个 Typealias 在 Swift 中的具体实践。
可读性
正如我们开篇所说,使用 Typealias 可以减轻阅读和维护负担。Typealias 可以赋予代码更多含义。当然,这并非总是必要的。对函数定义来说,函数的形参已经清楚地说明了参数的类型,
func orderDonuts(amount: Int) {
}
那么声明一个 Typealias 将造成额外的开销。
另一方面,对于变量和常量,它通常可以提高可读性并极大地改善文档。
简化闭包参数
我们知道 Swift 中的函数支持闭包参数,闭包参数最常见的使用就是尾随闭包。
func handle(action: (Int) -> Int) { ... }
这样子还是比较简洁的,但是当我们定义多个闭包参数时,如果如此定义,似乎显得代码很混乱
func handle(success: ((Int) -> Int)?,
failure: ((Error) -> Void)?,
progress: ((Double) -> Void)?) {
}
这时候,Typealias 就有用武之地了。
typealias Success = (Int) -> Int
typealias Failure = (Error) -> Void
typealias Progress = (Double) -> Void
func handle2(success: Success?, failure: Failure?, progress: Progress?) { ... }
当然你可以说这是可读性的另一种延伸,就想在 Objective-C 中我们在定义 block 时,我们习惯声明一个名称,方便我们的使用。
当我们需要在多个方法中使用这种复合类型的数据时,或者是我们需要使用其作为属性时,用于后续使用时:
typealias Success = (Int) -> Int
typealias Failure = (Error) -> Void
typealias Progress = (Double) -> Void
class MyHttpManager {
var successHandler:Success?
var failureHandler:Failure?
//...
func foo(success: Success, failure: Failure) {
if isSuccess {
success()
} else {
failure()
}
}
func bar(success: @escaping Success, failure: @escaping Failure) {
successHandler = success
failureHandler = failure
internalHandle()
}
// ...
func internalHandle() {
//...
}
}
与泛型结合
Typealias 还可以与泛型结合使用。
一般在网络请求时,我们的回调函数总会携带一些关于当前网络请求的相关信息,用于我们的 Debug 过程。
我们可以通过使用泛型处理程序来进一步改进我们上文的几个例子:
typealias Handler<Number> = (Number, HTTPResponse?, Context) -> Void
typealias Success = Handler<Int>
typealias Failure = Handler<Error>
typealias Progress = Handler<Double>
class MyManager {
var successHandler:Success?
var failureHandler:Failure?
//...
func foo(success: Success, failure: Failure) {
if isSuccess {
success()
} else {
failure()
}
}
func bar(success: @escaping Success, failure: @escaping Failure) {
successHandler = success
failureHandler = failure
internalHandle()
}
// ...
func internalHandle() {
//...
}
}
这样子,我们定义的Success
、Failure
和 Progress
能够传递更多的数据
这种方法对于自己的类型也非常有用。你可以创建一个泛型定义,然后定义详细的 Typealias :
enum Either<Left, Right> {
case left(Left)
case right(Right)
}
typealias Result<Value> = Either<Value, Error>
typealias IntOrString = Either<Int, String>
与元祖结合
类似地,可以使用泛型和元组来定义类型,而不必去定义一个 Struct。
typealias TypedUserInfoKey<T> = (key: String, type: T.Type)
let integerTypedKey = TypedUserInfoKey(key: "Foo", type: Int.self)
就跟声明结构体一样,我们似乎声明一个具有逐一成员构造器的元祖。
组合协议
有时会遇到这样的情况:存在多个协议,并且有一种特定的类型需要实现所有协议。
例如,在 Swift 中的 Codable
协议,就是 Decodable
和 Encodable
两个协议的 Typealias:
typealias Codable = Decodable & Encodable
通过这种组合协议的方式,我们的代码更具可读性。另一个例子就是:
// 载人
protocol CarriesPassengers { }
// 载货
protocol CarriesCargo { }
//陆地
protocol OnRoad { }
//水上
protocol OnWater { }
//车
typealias Car = CarriesPassengers & CarriesCargo & OnRoad
//船
typealias Boat = CarriesPassengers & CarriesCargo & OnWater
我们可以通过组合功能来实现不同的类的定义,这不失为一个解耦的好办法。
关联属性
我们在 Day11 - 协议和拓展 中讲解了协议中关联类型的用法。
protocol Identifiable {
associatedtype ID: Equatable & CustomStringConvertible
var id: ID { get }
}
struct Book: Identifiable {
let id: String
}
struct Clothes: Identifiable {
let id: Int
}
当我们不想在协议中声明一个遵循关联类型的用法时,我们可以使用 Typealias 来实现
protocol Identifiable {
associatedtype ID: Equatable & CustomStringConvertible
}
struct Book: Identifiable {
typealias ID = String
let id: ID
}
struct Clothes: Identifiable {
typealias ID = Int
let id: ID
}
总结
Typealias 最主要的目标可能会使为了使你的代码具备更好的可读性。但是其结合泛型和元组的使用可能会让你眼前一亮。
所以,当使用复合类型时,肯定会注意到 Typealias 的好处。不过需要注意的是,不要让你的别名妨碍到他人。
如果你有任何问题、评论或反馈,请随时联系。如果你愿意,可以通过分享这篇文章来让更多的人发现它。
感谢你阅读本文! 🚀