What is TypeAlias?
Typealias is rarely the first thing that comes to mind when recalling Swift’s powerful language features. However, there are many situations where type aliases can be useful. This article gives you a brief overview of what TypeAliases are, how to define them, and lists several examples of how to use them in your own code. Let’s dive in!
As the name implies, TypeAlias is an alias for a specific type. Type, such as Int, Double, UIViewController, or a custom type. Int32 and Int8 are different types. In other words, a type alias inserts another name for an existing type into your code base. Such as:
typealias Money = Int
Copy the code
Create an alias for Int. This allows you to use Money anywhere in your code, just like Int:
struct Bank { typealias Money = Int private var credit: Money = 0 mutating func deposit(amount: Money) { credit += amount } mutating func withdraw(amount: Money) { credit -= amount }}
Copy the code
There’s a structure on it called the Bank that manages the money. However, instead of using Int as the amount, we use the Money type. You can see that the += and -= operators still work as expected.
You can also mix type aliases and primitive types, and match both. We can do this because, to the Swift compiler, they all resolve to the same thing:
struct Bank { typealias DepositMoney = Int typealias WithdrawMoney = Int private var credit: Int = 0 mutating func deposit(amount: DepositMoney) { credit += amount } mutating func withdraw(amount: WithdrawMoney) { credit -= amount }}
Copy the code
Here we use a mixture of Int and its different custom type aliases, DepositMoney and 款 Money.
Generic type alias
In addition to the above, a type alias can also have generic parameters:
typealias MyArray<T> = Array<T>let newArray: MyArray = MyArray(arrayLiteral: 1, 2, 3)
Copy the code
Above, a type alias is defined for MyArray, just like a regular array. Finally, the generic parameters of a type alias can even have constraints. Imagine that we want our new MyArray to keep only types that follow StringProtocol:
typealias MyArray<T> = Array<T> where T: StringProtocol
Copy the code
This is a nice feature because you can quickly define an Array for a particular type without having to subclass Array. With that said, let’s look at some practical uses of type aliasing.
IOS Development Communication Technology Group: [563513413](Jq.qq.com/? \_wv= 1027&… , share BAT, Ali interview questions, interview experience, discuss technology, we exchange learning and growth together!
Practical application
Clearer code
The first, and obvious, use case has been briefly covered. Type aliases can make code more meaningful. In the TypeAlias Money = Int example, we introduce the Money type — a clear concept. Using it like let amount: Money = 0 is easier to understand than let amount: Int = 0. In the first example, you immediately know that this is
money
the
The amount of
. In the second example, it could be anything: the number of bikes, the number of characters, the number of donuts — who knows!
This is obviously not all necessary. If the function signature already clearly states the type of the parameter (func orderDonuts(amount: Int)), then including additional type identifiers would be unnecessary overhead. For variables and constants, on the other hand, it can generally improve readability and greatly aid in documentation.
Simpler optional closures
Optional closures in Swift are a bit clunky. The general definition of a closure that takes an Int and returns an Int looks like this:
func handle(action: (Int) -> Int) { ... }
Copy the code
Now, if you want to make this closure selectable, you can’t just add a question mark:
func handle(action: (Int) -> Int?) {... }Copy the code
After all, this is not a selectable closure, but rather
One returns an optional Int
The closures. The correct way is to add parentheses:
func handle(action: ((Int) -> Int)?) {... }Copy the code
This becomes particularly ugly if you have multiple closures like this. Below, there is a function that handles success and failure cases and calls an additional closure as the operation progresses.
func handle(success: ((Int) -> Int)? , failure: ((Error) -> Void)? , progress: ((Double) -> Void)?) {}Copy the code
This little piece of code contains
A lot of
Parentheses. Since we are not going to be Lisper, we want to solve this problem by using type aliases for different closures:
typealias Success = (Int) -> Inttypealias Failure = (Error) -> Voidtypealias Progress = (Double) -> Voidfunc handle2(success: Success? , failure: Failure? , progress: Progress?) {... }Copy the code
In fact, this function does look more readable. While this is nice, we do introduce additional syntax by using three lines of TypeAlias. But, it might actually help us in the long run, as we’ll see next.
defined
These specific types can be used for more than just the action handlers in the previous example. Here is an action handler class that has been slightly modified to be more practical:
final class Dispatcher { private var successHandler: ((Int) -> Void)? private var errorHandler: ((Error) -> Void)? func handle(success: ((Int) -> Void)? , error: ((Error) -> Void)?) { self.successHandler = success self.errorHandler = error internalHandle() } func handle(success: ((Int) -> Void)?) { self.successHandler = success internalHandle() } func handle(error: ((Int)-> Void?) ) { self.errorHandler = error internalHandle() } private func internalHandle() { ... }}Copy the code
This structure introduces two closures, one for the success case and one for the error case. However, we also want to provide more convenient functions that simply call one of the processors. In the example above, adding another parameter (such as HTTPResponse) to the success and error handlers would have required a lot of code change. In three places ((Int) -> Void)? ((Int, HTTPResponse) -> Void)? . The error handler is the same. This can be avoided by using multiple type aliases and changing the type in only one place:
final class Dispatcher { typealias Success = (Int, HTTPResponse) -> Void typealias Failure = (Error, HTTPResponse) -> Void private var successHandler: Success? private var errorHandler: Failure? func handle(success: Success? , error: Failure?) { self.successHandler = success self.errorHandler = error internalHandle() } func handle(success: Success?) { self.successHandler = success internalHandle() } func handle(error: Failure?) { self.errorHandler = error internalHandle() } private func internalHandle() { ... }}Copy the code
Not only is this easy to read, but it will continue to serve its purpose as the type is used in more places.
Generic alias
Type aliases can also be generic. A simple use case is to enforce containers with special meaning. Suppose we have an application that processes books. A book is made up of chapters and chapters are made up of pages. Basically, these are just arrays. Here is the TypeAlias:
struct Page {}typealias Chapter = Array<Page>typealias Book = Array<Chapter>
Copy the code
This has two advantages over just using arrays.
-
This code is more explanatory.
-
Wrap an array of pages
only
Can contain pages and not others.
Review our previous use
successful
and
failure
An example of a handler that we can further improve by using a generic handler:
typealias Handler<In> = (In, HTTPResponse? , Context) -> Voidfunc handle(success: Handler<Int>? , failure: Handler<Error>? , progress: Handler<Double>? .)Copy the code
It’s a great combination. This allows us to write a simpler function and edit the Handler in one place.
This approach is also useful for custom types. You can create a generic definition and then define the detailed type aliases:
struct ComputationResult<T> { private var result: T}typealias DataResult = ComputationResult<Data>typealias StringResult = ComputationResult<String>typealias IntResult = ComputationResult<Int>
Copy the code
Again, type aliases allow us to write less code and simplify definitions in code.
A function like tuple
Similarly, you can use generics and tuples to define types, rather than having to use structs. Below, we imagine a data type of genetic algorithm that can modify its value T over multiple generations.
typealias Generation<T: Numeric> = (initial: T, seed: T, count: Int, current: T)
Copy the code
If you define such a type alias, you can actually initialize it as if it were a structure:
let firstGeneration = Generation(initial: 10, seed: 42, count: 0, current: 10)
Copy the code
Although it does look like a structure, it is just a type alias for a tuple.
Combination of agreement
Sometimes, you will run into situations where you have multiple protocols and need to use a particular type to implement them all. This usually happens when you define a protocol layer to improve flexibility.
protocol CanRead {}protocol CanWrite {}protocol CanAuthorize {}protocol CanCreateUser {}typealias Administrator = CanRead & CanWrite & CanAuthorize & CanCreateUsertypealias User = CanRead & CanWritetypealias Consumer = CanRead
Copy the code
Here, we define the permissions layer. Administrators can do everything, users can read and write, and consumers can only read.
Association types
This is beyond the scope of this article, but the associated type of a protocol can also be defined by a type alias:
protocol Example { associatedtype Payload: Numeric}struct Implementation: Example { typealias Payload = Int}
Copy the code
disadvantages
Although type aliases are a very useful feature, they have a minor drawback: if you are not familiar with the code base, the following two definitions can be interpreted very differently.
func first(action: (Int, Error?) -> Void) {}func second(action: Success) {}
Copy the code
The second is not immediately clear. What type of Success is Success? How to construct it? You have to hold Option down and click on it in Xcode to see what it does and how it works. This creates extra work. If you use many type aliases, it will take more time. There is no good solution to this, and (usually) you have to rely on use cases.
Benedikt Terhechte, Author: Benedikt Terhechte Proofreading: WAMaker, Nemocdz; Finalized: Pancf