Swift — Generics

Arrays

There are two different ways to declare an array.

let numbers: [Int] = [1, 2, 3, 4]
let numbers: Array<Int> = [1, 2, 3, 4]

Optionals

A quick look at the standard library.

public enum Optional<Wrapped> : ExpressibleByNilLiteral {// The compiler has special knowledge of Optional<Wrapped>, including the fact// that it is an `enum` with cases named `none` and `some`./// The absence of a value./// In code, the absence of a value is typically written using the `nil`literal rather than the explicit `.none` enumeration case.case none/// The presence of a value, stored as `Wrapped`.case some(Wrapped)}
var number: Int? = 5
var number: Optional<Int> = 5

Usage in code

Generics are declared using <> with a nested keyword such as <Element> or <Wrapped>.

public struct Dictionary<Key: Hashable, Value>
class TableViewDataSource<Model>: UITableViewDataSource {
let model: [Model]
init(_ model: [Model]) {
self.model = model
}
}
struct Resource<DataModel> {
let url: URL
let parse: (AnyObject) -> DataModel
}
func fetch<DataModel>(
resource: Resource<DataModel>,
completionHandler: (DataModel) -> ()
) { }
protocol SomeProtocol {
associated type SomeGeneric
var someProperty: SomeGeneric
}
enum State<Content> {
case loading
case loaded([Content])
case error(Error)
}
let evens: State<Int> = .loaded([2, 4, 6, 8])
let names: State<String> = .loaded(["John", "Joe", "Jack"])

Generics constraints

Specific constraints can be set on generics in order to narrow down the scope of specialization.

enum Error: Swift.Error {
case url
case decode(Swift.Error)
case network(Swift.Error)
}
func fetch<Model: Decodable>(
withUrl url: URL?,
decodeType: Model.Type,
completionHandler: @escaping (Result<Model, Error>) -> ()
) {
guard let url = url else {
completionHandler(.failure(Error.url))
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// Your session’s datatask here.
let data = Data() do { let models = try JSONDecoder().decode(
decodeType,
from:data
)
completionHandler(.success(models)) } catch let error {
completionHandler(.failure(Error.decode(error)))
}
}
}
func fetch<Model: Decodable>
func fetch<Model> where Model: Decodable

Generics vs Any.

Although the comparison is legitimate, Any and Generics behave differently.

Phantom types

Phantom types are used to provides additional safety in your code.

class NetworkManager {    func fetch<Model>(
withUrl url: URL?,
decodeType: Model.Type,
completionHandler: @escaping (Result<Model, Error>) -> ()
) where Model: Decodable {
guard let url = url else {
completionHandler(.failure(Error.url))
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// session’s datatask here.
let data = Data() do {
let model = try JSONDecoder().decode(
decodeType,
from: data
)
completionHandler(.success(model)) } catch let error {
completionHandler(.failure(Error.decode(error)))
}
}
}
func fetch<Model>(
withUrl url: URL?,
decodeType: Model.Type,
completionHandler: @escaping (Result<[Model], Error>) -> ()
) where Model: Decodable {
guard let url = url else {
completionHandler(.failure(Error.url))
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { // session’s datatask here. let data = Data() do {
let models = try JSONDecoder().decode(
decodeType,
from: data
)
completionHandler(.success([models])) } catch let error {
completionHandler(.failure(Error.decode(error)))
}
}
}
func fetch<Model>(
withUrl url: URL?,
decodeType: Model.Type,
completionHandler: @escaping (Result<[Model], Error>) -> ()
) where Model: Decodable {
guard let url = url else {
completionHandler(.failure(Error.url))
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// session’s datatask here.
let data = Data() do {
let models = try JSONDecoder().decode(
decodeType,
from: data
)
completionHandler(.success([models])) } catch let error {
completionHandler(.failure(Error.decode(error)))
}
}
}
protocol AuthorizationLevel {
static var accessToken: String { get }
}
struct User: AuthorizationLevel {
static var accessToken: String {
return "some user token"
}
}
struct Manager: AuthorizationLevel {
static var accessToken: String {
return "some manager token"
}
}
struct Admin: AuthorizationLevel {
static var accessToken: String {
return "some admin token"
}
}
struct Resource<Model: Decodable, Access: AuthorizationLevel> {
let model: Model.Type
let url: URL?
}
class NetworkManager {
static let shared = NetworkManager()
private init() {} func fetch<Model>(
resource: Resource<Model, User>,
completionHandler: @escaping (Result<Model, Error>) -> ()
) {
guard let url = resource.url else {
completionHandler(.failure(Error.url))
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// session’s datatask here.
let data = Data() do {
let model = try JSONDecoder().decode(
resource.model,
from: data
)
completionHandler(.success(model)) } catch let error {
completionHandler(.failure(Error.decode(error)))
}
}
}
func fetch<Model>(
resource: Resource<Model, Manager>,
completionHandler: @escaping (Result<[Model], Error>) -> ()
) {
guard let url = resource.url else {
completionHandler(.failure(Error.url))
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// session’s datatask here.
let data = Data() do {
let models = try JSONDecoder().decode(
resource.model,
from: data
)
completionHandler(.success([models])) } catch let error {
completionHandler(.failure(Error.decode(error)))
}
}
}
func fetch<Model>(
resource: Resource<Model, Admin>,
completionHandler: @escaping (Result<[Model], Error>) -> ()
) {
guard let url = resource.url else {
completionHandler(.failure(Error.url))
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// session’s datatask here.

let data = Data()
do {
let models = try JSONDecoder().decode(
resource.model,
from: data
)
completionHandler(.success([models])) } catch let error {
completionHandler(.failure(Error.decode(error)))
}
}
}
}
struct DataModel: Decodable {
let property: String
}
let resource = Resource<DataModel, User>(
model: DataModel.self,
url: URL(string: “”)!
)
NetworkManager.shared.fetch(resource: resource) { result in
guard let model = try? result.get() else {
return
}
// do something with model
}

Conclusion

The use of generic « types that can work with any type » is very powerful when it comes to writing reusable code while allowing specialization.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store