Difficulty: Beginner | Easy | Normal | Challenging
This article has been developed using Xcode 11.4.1, and Swift 5.2.2
This article is example laden, and shows how I've used Type Erasure
in my production Apps. It should help if you wish to know what Type Erasure
is and how you might apply it in your Swift code.
- You will be expected to be aware how to make a Single View Application, or a Playground to run Swift code
- You will need to know about protocols in Swift
- The heart of this article is a constant (and annoying) talk about generics
Type Erasure: Removing type information
When programming in Swift we have a type-safe language, that is Swift is strongly typed.
Now sometimes you might want to use type erasure, that is removing type information to find out why you might want to lose information in this way we have to think about protocol-oriented programming and specific implementations of the same...
When we code against an interface (in Swift we code against a protocol
) we develop a contract between the client and supplier of a service.
I've previously used associated types to create articles on Operation queues where I've a protocol
with an associated type
to make the protocol
in some sense generic.
In that instance, my HTTPManager
conforms to a HTTPManagerProtocol
which can then be represented as a stored property, for example in my UserListRetrievalOperation
class:
class UserListRetrievalOperation<T: HTTPManagerProtocol>: NetworkOperation {
var dataFetched: Data?
var httpManager: T?
var error: Error?
var url: URL?
init(url: URL? = nil, httpManager: T) {
self.url = url
self.httpManager = httpManager
}
override func main() {
guard let url = url else {return}
httpManager?.get(url: url, completionBlock: { data in
switch data {
case .failure(let error):
self.error = error
self.complete(result: data)
case .success(let successdata):
self.dataFetched = successdata
self.complete(result: data)
}
})
}
}
where, inevitably the class must itself be generic with <T: HTTPManagerProtocol>
being added to the class signature. The reason for this is that we need to save an instance of type T
, and that means we need to be able to specify what type that property will be. The danger with this approach is that we are looking down the barrel of making everything generic. If you want to take a look at this approach in situ please do take a look at the full article , where generic protocols
have been used to allow full testing of the classes that have been used.
Now this approach isn't a problem per se (it met it's goals, and delivered a rather spiffing article), but we can do better. You don't need to guess: A better approach is using Type Erasure
.
This particular article is about Type Erasure, and therefore I'm repurposing my favourite basic network manager to simply make a network request.
A little respect
The approach I'd usually take is MVVM-C or similar, perhaps making network requests from the coordinator. However, for this article I'm attempting to make everything as simple as possible - and we will make network requests from right in the UIViewController
. The reason for this is that making this a discrete article, away from architecture should make Type Erasure
easier to understand without the use of any particular archiecture (for those interested, this is trivial to implement in other architectures).
I've gone for an approach not using the storyboard
so I can inject the network manager using the same mechanism for testing and production code.
So we can pass through this step-by-step (although please do download the code from the repo, that's what it is there for!) Initialise the VC
let windowScene = UIWindowScene(session: session, connectionOptions: connectionOptions)
self.window = UIWindow(windowScene: windowScene)
let network = HTTPManager(session: URLSession.shared)
let initialVC = ViewController<HTTPManager<URLSession>>(network: network)
self.window?.rootViewController = initialVC
self.window?.makeKeyAndVisible()
You see the problem - that ViewController<HTTPManager<URLSession>>(network: network)
makes me feel a little sick.
The view controller can be generic, and store the network manager
class ViewController<U: HTTPManagerProtocol>: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func loadView() {
view = UIView()
view.backgroundColor = .red
}
var httpManager: U?
init<T: HTTPManagerProtocol>(network: T ) {
super.init(nibName: nil, bundle: nil)
let url = URL(string: "https://httpbin.org/get")!
httpManager = network as? U
network.get(url: url, completionBlock: { res in
print ("use the returned data \(res)")
})
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
but uggh. We have to continue to drag the associated type around with us, and this will continue up until it pollutes the AppDelegate and/or scene delegate.
We are going to use a type erasure technique commonly used in the Swift standard libarary - SwiftUI in particular has the AnyPublisher
type that most who have touched SwiftUI have become familiar with.
We say that we wrap a protocol (which has an associated value) into a generic type, which can then be used without making each hosting object generic.
The key:
class AnyHTTPManager<U>: HTTPManagerProtocol {
let session: U
let closure: (URL, @escaping (Result<Data, Error>) -> Void) -> ()
func get(url: URL, completionBlock: @escaping (Result<Data, Error>) -> Void) {
closure(url, completionBlock)
}
init<T: HTTPManagerProtocol>(manager: T) {
closure = manager.get
session = manager.session as! U
}
}
This is now our main class for the HTTPManager
In the SceneDelegate
in the example I've instantiated the HTTPManager and then injected this into the view controller (I'd usually put this into the ViewModel, but this example is meant to be easy and it is certainly not production-ready).
let network = HTTPManager(session: URLSession.shared)
let initialVC = ViewController(network: network)
I've then created an Initializer in the View Controller
that lets us store the HTTPManager
- as an AnyHTTPManager
with associated type
var httpManager: AnyHTTPManager<URLSession>?
init<T: HTTPManagerProtocol>(network: T) {
self.httpManager = AnyHTTPManager(manager: network)
super.init(nibName: nil, bundle: nil)
}
which means that then we can make network calls from within any particular function in (this case) the view controller
httpManager?.get(url: url, completionBlock: { result in
print ("use the returned data \(result)")
})
Look - there's a nice repo that enables you to see all of the code that is avaliable.
In any case, Type Erasure is Cool!
It seems surprising that losing information is actually useful - but that is exactly what the type erasure is.
That is, it erases the type.
Does that make it easier to understand what AnyPublisher
is in Swift? Tell me.
In any case, have a nice day.
If you've any questions, comments or suggestions please hit me up on Twitter