Swift 中 Typealias 的使用

1,683 阅读6分钟

点赞评论,感觉有用的朋友可以关注笔者公众号 iOS 成长指北,持续更新

从语义上说,Swift 中 Typealias 就是现有类的别名。当我们谈及 Swift 中的语言特性时,Typealias 往往不够起眼。

提及 Typealias ,一个简单的作用就是给现有类添加别名,为了减轻阅读和维护负担,我们给一些特定用处的实例使用别名作为类型:

import UIKit

typealias Location = CGPoint
typealias Distance = Double

诚然,这有点作用,但仅仅只为了添加一个别名,对你来说可能没那么有用!

今天我们详细了解一下 Swift 中的 Typealias 。通过巧妙地的方法,在你的代码库中会非常有用。

希望今天的内容能够足够干。

学习目标

通过完成本文的学习,我们主要解决以下几个问题

  1. 什么是 Typealias?Typealias 是一种新类型吗?
  2. 从实践出发,我们能用 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() {
        //...
    }
}

这样子,我们定义的SuccessFailureProgress 能够传递更多的数据

这种方法对于自己的类型也非常有用。你可以创建一个泛型定义,然后定义详细的 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 协议,就是 DecodableEncodable 两个协议的 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 的好处。不过需要注意的是,不要让你的别名妨碍到他人


如果你有任何问题、评论或反馈,请随时联系。如果你愿意,可以通过分享这篇文章来让更多的人发现它。

感谢你阅读本文! 🚀