Skip to main content

Command Palette

Search for a command to run...

Typealias: Swift's underestimated champion

Updated
5 min read
Typealias: Swift's underestimated champion
D

I love technology and everything related, from gadgets to new professional techniques. I like thinking, researching, optimizing, inventing and developing. I have a strong background in software research and development, operating systems, Voice-over-IP, network security, wired and wireless network engineering, complemented with electronic engineering background.

My career goal is to always keep learning, to be challenged, and to work remotely so I can be present for my family.

Bug hacker and master troubleshooter, my strength is understanding a problem and getting to the root of it. I'm mostly a self-taught individual and a constant learner. I push my technical boundaries daily and search for ways to improve my skills every day. With over 20 years of experience writing software in various languages, creating or optimizing algorithms, the digital development world is my turf.

Sample challenges which I particularly enjoyed:

  • Created a GLSL based magnification tool for a client who was turned down by three other companies as "impossible to do on macOS".
  • Optimized several SQL queries to reduce load time of a particular web page from several seconds to sub 50ms.
  • Identified the root cause of stuttering animations in iOS mobile app and implemented mitigation strategy

Specialties: Swift, Objective-C and PHP Software Development; TCP/IP and Wireless Network Engineering

I've been aware of typealias for a long time. It's only recently that I really started to understand just how powerful this keyword can be. Let me share why I think they are the underestimated champion for unit tests and dependency injection.

Reference Use Case

A class depends on the services of another class. Your first instinct will likely be to create a protocol that will define the interface that you will require.

struct GroceryList: Codable {
    var items: [String]
}

protocol GroceryListLoader() {
    func loadFromURL(_ url: URL) throws -> GroceryList
}

class MyGroceryListManager {
    let loader: GroceryListLoader
    var groceryLists = [Name: GroceryList]()

    init(loader: GroceryListLoader) {
        self.loader = loader
    }

    func openList(name: String) throws {
        let listUrl = URL(string: "file://path/to/\(name).json")!
        groceryLists[name] = try loader.loadFromUrl(listUrl)
    }
}

This allows you to replace the implementation of the GroceryListLoader with a mock/stub during unit tests. It also makes your code more flexible as it can work with any number of implementations as long as they can conform to the GroceryListLoader protocol.

Reference Case Unit Test

Let's create a small unit test to demonstrate how this is typically implemented:

class GroceryListLoaderMock: GroceryListLoader {
    enum Errors: Error {
      case fileNotFound
    }

    var lists = [URL: GroceryList]()
    func loadFromURL(_ url: URL) throws -> GroceryList {
        guard let list = lists[url] else {
            throw Errors.fileNotFound
        }
        return list
    }
}

class GroceryListManagerTests: XCTestCase {
    func testThrowsWhenFileIsMissing() {
        let mockLoader = GroceryListLoaderMock()
        let manager = GroceryListManager(loader: mockLoader)
        XCTAssertThrow(try manager.openList(name: "Walmart"))
    }
}

Nothing unusual here. When executing testThrowsWhenFileIsMissing() a mockLoader is created, and since no list was assigned to the mock's lists, the loadFromURL() function will throw the Errors.fileNotFound. In turn the GroceryListManager implementation of openList() because it doesn't capture any error throw by the loader, will simply allow the thrown error to carry on to the caller of the openList() function therefore passing the test.

Typealias alternative

struct GroceryList: Codable {
    var items: [String]
}

typealias LoadGroceryList = (_ url: URL) throws -> GroceryList

class GroceryListManager {
    let loader: LoadGroceryList
    var groceryLists = [Name: GroceryList]()

    init(loader: @escaping LoadGroceryList) {
        self.loader = loader
    }

    func openList(name: String) throws {
        let listUrl = URL(string: "file://path/to/\(name).json")!
        groceryLists[name] = try loader(listUrl)
    }
}

Here, instead of defining a protocol, we define a typealias with the signature of the closure we need. In the GroceryListManager we will store a reference to that closure, ensuring we tag it as @escaping since it will be used outside the scope of init(). Finally, in openList() we execute the closure to receive the list at the given URL.

Typealias Unit Test

class GroceryListManagerTests: XCTestCase {
    func testThrowsWhenFileIsMissing() {
        enum Errors: Error {
           case fileNotFound
        }
        let mockLoader: LoadGroceryList = { _ in throw Errors.fileNotFound }
        let manager = GroceryListManager(loader: mockLoader)
        XCTAssertThrow(try manager.openList(name: "Walmart"))
    }
}

Because the typealias defines the closure signature needed to fulfill the requirements needed by GroceryListManager class, we no longer need a mock class to be created. We can directly implement a custom implementation needed in the closure.

It could even be simplified to the following:

class GroceryListManagerTests: XCTestCase {
    func testThrowsWhenFileIsMissing() {
        enum Errors: Error {
           case fileNotFound
        }
        let manager = GroceryListManager(
            loader: { _ in throw Errors.fileNotFound })
        XCTAssertThrow(try manager.openList(name: "Walmart"))
    }
}

As you can see, the unit test is much easier to comprehend, as we no longer depend on the specific implementation of the Mock.

Declaring conformance in another class

In reality, many of your classes may expose multiple functions. You can still be empowered to use typealias, for example:

typealias LoadGroceryList = (URL) throws -> GroceryList

class MyGroceryListLoader {
   let loadFromUrl: LoadGroceryList = loadGroceryList

   private func loadGroceryList(_ url: URL) throws -> GroceryList {
     ...
   }
}

You can also define protocol conformance like you used to if needed:

typealias LoadGroceryList = (URL) throws -> GroceryList
typealias WriteGroceryList = (GroceryList, URL) throws -> Void

protocol GroceryListLoaderWriter {
   var loadFromURL: LoadGroceryList { get }
   var writeToURL: WriteGroceryList { get }
}

class MyGroceryListLoaderWriter: GroceryListLoaderWriter {
   let loadFromURL: LoadGroceryList = loadGroceryList
   let writeToURL: WriteGroceryList = writeGroceryList

   private func loadGroceryList(_ url: URL) throws -> GroceryList {
      ...
   }

   private func writeGroceryList(_ groceryList: GroceryList, _ url: URL) throws {
      ...
   }
}

class MyGroceryListManager {
   let loader: GroceryListLoaderWriter
   var openedLists = [Name: GroceryList]()

   init(loader: GroceryListLoaderWriter) {
      self.loader = loader
   }

   func openList(name: String) throws {
      let listUrl = URL(string: "/path/to/\(name).json")!
      openedLists[name] = try loader.loadFromUrl(listUrl)
   }
}

let myLoaderWriter = MyGroceryListLoaderWriter()
let myManager = MyGroceryListManager(loader: myLoaderWriter)

In the above case however, we can notice that MyGroceryListManager doesn't use the writer, only the reader, so it can be simplified to:

class MyGroceryListManager {
   let loader: LoadGroceryList
   var openedLists = [Name: GroceryList]()

   init(loader: @escaping LoadGroceryList) {
      self.loader = loader
   }

   func openList(name: String) throws {
      let listUrl = URL(string: "/path/to/\(name).json")!
      openedLists[name] = try loader(listUrl)
   }
}

let myLoaderWriter = MyGroceryListLoaderWriter()
let myManager = MyGroceryListManager(loader: myLoaderWriter.loadFromUrl)

Conclusion

Using typealias can provide you with additional flexibility and may end up requiring fewer lines of code in your app and in your unit tests.

If your class depends on the entirety of the protocol, then please continue to use your protocols like you are used to. However, if your class depends only on a subset of your protocol, a typealias may just be the tool that will allow you to simplify your unit tests and your dependency injections.

More from this blog

B

Blog on Mobile app security and reliability on iOS and macOS.

16 posts

I'm a passionate senior iOS & macOS developer with over 13 years in the Apple ecosystem and 25 years in the software industry. I have a special interest for data security and privacy compliance!