A glimpse into Protocols with
Associated Type and type Erasure
Denis Poifol
Cocoaheads Lyon, le 17 Mai 2018
2//
Sommaire
01 //
02 //
03 //
Protocols with Associated Type (PAT)
Type wrapping
Type Erasure
3//
01 //
Protocols with Associated Type (PAT)
4//
protocol Identifiable {
associatedtype Identifier
var id: Identifier { get }
}
struct IntIdentifiableStruct: Identifiable {
typealias Identifier = Int
let id: Int
/* Other properties */
}
struct StringIdentifiableStruct: Identifiable {
let id: String
/* Other properties */
}
Declaration and implementation
5//
extension Equatable where Self: Identifiable, Self.Identifier: Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.id == rhs.id
}
}
Extension
6//
extension Equatable where Self: Identifiable, Self.Identifier: Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.id == rhs.id
}
}
extension Hashable where Self: Identifiable, Self.Identifier: Hashable {
var hashValue: Int { return id.hashValue }
}
Extension
7//
extension Equatable where Self: Identifiable, Self.Identifier: Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.id == rhs.id
}
}
extension Hashable where Self: Identifiable, Self.Identifier: Hashable {
var hashValue: Int { return id.hashValue }
}
extension StringIdentifiableStruct: Hashable {}
Extension
8//
let identifiable: Identifiable Protocol 'Identifiable' can only be used
as a generic constraint because it has Self or associated type requirements
Existential
9//
Protocol with Associated Types
● Default protocol functionalities
● Placeholder for one or multiple types
● No existential type
● A given type can conform to a PAT only in
one way
Note: Protocol with associated type are not generic protocols (which do not
exist in swift)
10//
02 //
Type Wrapping
11//
Configurable protocol
protocol Configurable {
associatedtype Model
func configure(with model: Model)
}
12//
Configurable protocol
protocol Configurable {
associatedtype Model
func configure(with model: Model)
}
extension UILabel: Configurable {
func configure(with model: String) {
text = model
}
}
13//
Configurable protocol
protocol Configurable {
associatedtype Model
func configure(with model: Model)
}
extension UILabel: Configurable {
func configure(with model: String) {
text = model
}
}
extension UIButton: ConfigurableView {
func configure(with model: String) {
setTitle(model, for: .normal)
}
}
14//
Wrap the implementing class inside of a generic type
struct ConfigurableWrapper<ConcreteClass: Configurable>: Configurable {
typealias Model = ConcreteClass.Model
private let wrappedInstance: ConcreteClass
init(_ wrappedInstance: ConcreteClass) {
self.wrappedInstance = wrappedInstance
}
func configure(with model: Model) {
wrappedInstance.configure(with: model)
}
}
15//
Wrap the implementing class inside of a generic type
let label = UILabel()
let configurable = ConfigurableWrapper(label)
type(of: configurable)// ConfigurableWrapper<UILabel>
16//
03 //
Type Erasure
17//
Type Erasure
What do we want to achieve ?
Store object conforming to a PAT regardless of
their actual type but defined by their associated
type(s)
18//
What do we want to achieve
protocol ViewContract {
var successView: AnyConfigurable<String> { get }
var goalView: AnyConfigurable<String> { get }
var successImageView: AnyConfigurable<UIImage> { get }
}
19//
What do we want to achieve
protocol ViewContract {
var successView: AnyConfigurable<String> { get }
var goalView: AnyConfigurable<String> { get }
var successImageView: AnyConfigurable<UIImage> { get }
}
var stringConfigurables: [AnyConfigurable<String>] = [
successUIElement,
goalUIElement,
]
let models = [
"Success",
"Present view to data layer wrapped as AnyConfigurable",
]
zip(stringConfigurable, models)
.forEach { $0.0.configure(with: $0.1) }
20//
Configurable
Type Erasure Pattern in Standard Library
Protocol
Associated
Type
Concrete
Type
21//
Configurable
String
UIImage
UILabel
UIImageView
UIButton
Type Erasure Pattern in Standard Library
Protocol
Associated
Type
Concrete
Type
22//
Configurable
String
UIImage
UILabel
UIImageView
UIButton
Type Erasure Pattern in Standard Library
Abstract Base
Private Box
Public Wrapper
Protocol
Associated
Type
Concrete
Type
23//
Configurable
String
UIImage
UILabel
UIImageView
UIButton
class _AnyConfigurableBase<ModelObject>: Configurable
Type Erasure Pattern in Standard Library
Abstract Base
Private Box
Public Wrapper
Protocol
Associated
Type
Concrete
Type
24//
Configurable
String
UIImage
UILabel
UIImageView
UIButton
class _AnyConfigurableBase<ModelObject>: Configurable
class _AnyConfigurableBox<ActualType: Configurable>:_AnyConfigurableBase<ActualType.Model>
init(_ configurable: ActualType)
Type Erasure Pattern in Standard Library
Abstract Base
Private Box
Public Wrapper
Protocol
Associated
Type
Concrete
Type
25//
Configurable
String
UIImage
UILabel
UIImageView
UIButton
class _AnyConfigurableBase<ModelObject>: Configurable
class _AnyConfigurableBox<ActualType: Configurable>:_AnyConfigurableBase<ActualType.Model>
init(_ configurable: ActualType)
final class AnyConfigurable<Model>: Configurable
init<Concrete: Configurable>(_ concrete: Concrete) where Concrete.Model == Model
Type Erasure Pattern in Standard Library
Abstract Base
Private Box
Public Wrapper
Protocol
Associated
Type
Concrete
Type
26//
Configurable
String
UIImage
UILabel
UIImageView
UIButton
class _AnyConfigurableBase<ModelObject>: Configurable
class _AnyConfigurableBox<ActualType: Configurable>:_AnyConfigurableBase<ActualType.Model>
init(_ configurable: ActualType)
final class AnyConfigurable<Model>: Configurable
init<Concrete: Configurable>(_ concrete: Concrete) where Concrete.Model == Model
Type Erasure Pattern in Standard Library
Abstract Base
Private Box
Public Wrapper
Protocol
Associated
Type
Concrete
Type
27//
Configurable
String
UIImage
UILabel
UIImageView
UIButton
class _AnyConfigurableBase<ModelObject>: Configurable
class _AnyConfigurableBox<ActualType: Configurable>:_AnyConfigurableBase<ActualType.Model>
init(_ configurable: ActualType)
final class AnyConfigurable<Model>: Configurable
init<Concrete: Configurable>(_ concrete: Concrete) where Concrete.Model == Model
Type Erasure Pattern in Standard Library
Abstract Base
Private Box
Public Wrapper
Protocol
Associated
Type
Concrete
Type
28//
Implementing the abstract base class
class _AnyConfigurableBase<ModelObject>: Configurable {
typealias Model = ModelObject
}
29//
Implementing the abstract base class
class _AnyConfigurableBase<ModelObject>: Configurable {
typealias Model = ModelObject
init() {
guard type(of: self) != _AnyConfigurableBase.self else {
fatalError("This class cannot be implemented")
}
}
func configure(with model: ModelObject) {
fatalError("Must be overiden")
}
}
30//
Create an amphibological box class
class _AnyConfigurableBox<ActualType: Configurable>:
_AnyConfigurableBase<ActualType.Model> {
private let configurable: ActualType
init(_ configurable: ActualType) {
self.configurable = configurable
}
override func configure(with model: Model) {
configurable.configure(with: model)
}
}
31//
Implementing the final wrapper
final class AnyConfigurable<Model>: Configurable {
private let box: _AnyConfigurableBase<Model>
init<Concrete: Configurable>(_ concrete: Concrete)
where Concrete.Model == Model {
box = _AnyConfigurableBox(concrete)
}
func configure(with model: Model) {
box.configure(with: model)
}
}
32//
What do we want to achieve
protocol ViewContract {
var successView: AnyConfigurable<String> { get }
var goalView: AnyConfigurable<String> { get }
var successImageView: AnyConfigurable<UIImage> { get }
}
var stringConfigurables: [AnyConfigurable<String>] = [
successUIElement,
goalUIElement,
]
let models = [
"Success",
"Present view to data layer wrapped as AnyConfigurable",
]
zip(stringConfigurable, models)
.forEach { $0.0.configure(with: $0.1) }
33//
Implementing ViewContract
class ViewController: UIViewController, ViewContract {
private(set) lazy var successView = AnyConfigurable(label)
private(set) lazy var goalView = AnyConfigurable(button)
private(set) lazy var successImageView = AnyConfigurable(imageView)
private var label = UILabel()
private var button = UIButton()
private var imageView = UIImageView()
// ViewController implementation
}
34//
Read More
Great article from bigNerdRanch that inspired this talk
https://www.bignerdranch.com/blog/breaking-down-type-erasures-in-swift/
Swift Generic Manifesto
https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md
My github with a playground related to this talk
https://github.com/denisPoifol/Talks/
Merci.
35//
Denis Poifol
Junior Software Engineer
+33 6 58 90 85 54
denis.poifol@fabernovel.com

A glimpse into protocols with associated type and type erasure

  • 1.
    A glimpse intoProtocols with Associated Type and type Erasure Denis Poifol Cocoaheads Lyon, le 17 Mai 2018
  • 2.
    2// Sommaire 01 // 02 // 03// Protocols with Associated Type (PAT) Type wrapping Type Erasure
  • 3.
    3// 01 // Protocols withAssociated Type (PAT)
  • 4.
    4// protocol Identifiable { associatedtypeIdentifier var id: Identifier { get } } struct IntIdentifiableStruct: Identifiable { typealias Identifier = Int let id: Int /* Other properties */ } struct StringIdentifiableStruct: Identifiable { let id: String /* Other properties */ } Declaration and implementation
  • 5.
    5// extension Equatable whereSelf: Identifiable, Self.Identifier: Equatable { static func ==(lhs: Self, rhs: Self) -> Bool { return lhs.id == rhs.id } } Extension
  • 6.
    6// extension Equatable whereSelf: Identifiable, Self.Identifier: Equatable { static func ==(lhs: Self, rhs: Self) -> Bool { return lhs.id == rhs.id } } extension Hashable where Self: Identifiable, Self.Identifier: Hashable { var hashValue: Int { return id.hashValue } } Extension
  • 7.
    7// extension Equatable whereSelf: Identifiable, Self.Identifier: Equatable { static func ==(lhs: Self, rhs: Self) -> Bool { return lhs.id == rhs.id } } extension Hashable where Self: Identifiable, Self.Identifier: Hashable { var hashValue: Int { return id.hashValue } } extension StringIdentifiableStruct: Hashable {} Extension
  • 8.
    8// let identifiable: IdentifiableProtocol 'Identifiable' can only be used as a generic constraint because it has Self or associated type requirements Existential
  • 9.
    9// Protocol with AssociatedTypes ● Default protocol functionalities ● Placeholder for one or multiple types ● No existential type ● A given type can conform to a PAT only in one way Note: Protocol with associated type are not generic protocols (which do not exist in swift)
  • 10.
  • 11.
    11// Configurable protocol protocol Configurable{ associatedtype Model func configure(with model: Model) }
  • 12.
    12// Configurable protocol protocol Configurable{ associatedtype Model func configure(with model: Model) } extension UILabel: Configurable { func configure(with model: String) { text = model } }
  • 13.
    13// Configurable protocol protocol Configurable{ associatedtype Model func configure(with model: Model) } extension UILabel: Configurable { func configure(with model: String) { text = model } } extension UIButton: ConfigurableView { func configure(with model: String) { setTitle(model, for: .normal) } }
  • 14.
    14// Wrap the implementingclass inside of a generic type struct ConfigurableWrapper<ConcreteClass: Configurable>: Configurable { typealias Model = ConcreteClass.Model private let wrappedInstance: ConcreteClass init(_ wrappedInstance: ConcreteClass) { self.wrappedInstance = wrappedInstance } func configure(with model: Model) { wrappedInstance.configure(with: model) } }
  • 15.
    15// Wrap the implementingclass inside of a generic type let label = UILabel() let configurable = ConfigurableWrapper(label) type(of: configurable)// ConfigurableWrapper<UILabel>
  • 16.
  • 17.
    17// Type Erasure What dowe want to achieve ? Store object conforming to a PAT regardless of their actual type but defined by their associated type(s)
  • 18.
    18// What do wewant to achieve protocol ViewContract { var successView: AnyConfigurable<String> { get } var goalView: AnyConfigurable<String> { get } var successImageView: AnyConfigurable<UIImage> { get } }
  • 19.
    19// What do wewant to achieve protocol ViewContract { var successView: AnyConfigurable<String> { get } var goalView: AnyConfigurable<String> { get } var successImageView: AnyConfigurable<UIImage> { get } } var stringConfigurables: [AnyConfigurable<String>] = [ successUIElement, goalUIElement, ] let models = [ "Success", "Present view to data layer wrapped as AnyConfigurable", ] zip(stringConfigurable, models) .forEach { $0.0.configure(with: $0.1) }
  • 20.
    20// Configurable Type Erasure Patternin Standard Library Protocol Associated Type Concrete Type
  • 21.
    21// Configurable String UIImage UILabel UIImageView UIButton Type Erasure Patternin Standard Library Protocol Associated Type Concrete Type
  • 22.
    22// Configurable String UIImage UILabel UIImageView UIButton Type Erasure Patternin Standard Library Abstract Base Private Box Public Wrapper Protocol Associated Type Concrete Type
  • 23.
    23// Configurable String UIImage UILabel UIImageView UIButton class _AnyConfigurableBase<ModelObject>: Configurable TypeErasure Pattern in Standard Library Abstract Base Private Box Public Wrapper Protocol Associated Type Concrete Type
  • 24.
    24// Configurable String UIImage UILabel UIImageView UIButton class _AnyConfigurableBase<ModelObject>: Configurable class_AnyConfigurableBox<ActualType: Configurable>:_AnyConfigurableBase<ActualType.Model> init(_ configurable: ActualType) Type Erasure Pattern in Standard Library Abstract Base Private Box Public Wrapper Protocol Associated Type Concrete Type
  • 25.
    25// Configurable String UIImage UILabel UIImageView UIButton class _AnyConfigurableBase<ModelObject>: Configurable class_AnyConfigurableBox<ActualType: Configurable>:_AnyConfigurableBase<ActualType.Model> init(_ configurable: ActualType) final class AnyConfigurable<Model>: Configurable init<Concrete: Configurable>(_ concrete: Concrete) where Concrete.Model == Model Type Erasure Pattern in Standard Library Abstract Base Private Box Public Wrapper Protocol Associated Type Concrete Type
  • 26.
    26// Configurable String UIImage UILabel UIImageView UIButton class _AnyConfigurableBase<ModelObject>: Configurable class_AnyConfigurableBox<ActualType: Configurable>:_AnyConfigurableBase<ActualType.Model> init(_ configurable: ActualType) final class AnyConfigurable<Model>: Configurable init<Concrete: Configurable>(_ concrete: Concrete) where Concrete.Model == Model Type Erasure Pattern in Standard Library Abstract Base Private Box Public Wrapper Protocol Associated Type Concrete Type
  • 27.
    27// Configurable String UIImage UILabel UIImageView UIButton class _AnyConfigurableBase<ModelObject>: Configurable class_AnyConfigurableBox<ActualType: Configurable>:_AnyConfigurableBase<ActualType.Model> init(_ configurable: ActualType) final class AnyConfigurable<Model>: Configurable init<Concrete: Configurable>(_ concrete: Concrete) where Concrete.Model == Model Type Erasure Pattern in Standard Library Abstract Base Private Box Public Wrapper Protocol Associated Type Concrete Type
  • 28.
    28// Implementing the abstractbase class class _AnyConfigurableBase<ModelObject>: Configurable { typealias Model = ModelObject }
  • 29.
    29// Implementing the abstractbase class class _AnyConfigurableBase<ModelObject>: Configurable { typealias Model = ModelObject init() { guard type(of: self) != _AnyConfigurableBase.self else { fatalError("This class cannot be implemented") } } func configure(with model: ModelObject) { fatalError("Must be overiden") } }
  • 30.
    30// Create an amphibologicalbox class class _AnyConfigurableBox<ActualType: Configurable>: _AnyConfigurableBase<ActualType.Model> { private let configurable: ActualType init(_ configurable: ActualType) { self.configurable = configurable } override func configure(with model: Model) { configurable.configure(with: model) } }
  • 31.
    31// Implementing the finalwrapper final class AnyConfigurable<Model>: Configurable { private let box: _AnyConfigurableBase<Model> init<Concrete: Configurable>(_ concrete: Concrete) where Concrete.Model == Model { box = _AnyConfigurableBox(concrete) } func configure(with model: Model) { box.configure(with: model) } }
  • 32.
    32// What do wewant to achieve protocol ViewContract { var successView: AnyConfigurable<String> { get } var goalView: AnyConfigurable<String> { get } var successImageView: AnyConfigurable<UIImage> { get } } var stringConfigurables: [AnyConfigurable<String>] = [ successUIElement, goalUIElement, ] let models = [ "Success", "Present view to data layer wrapped as AnyConfigurable", ] zip(stringConfigurable, models) .forEach { $0.0.configure(with: $0.1) }
  • 33.
    33// Implementing ViewContract class ViewController:UIViewController, ViewContract { private(set) lazy var successView = AnyConfigurable(label) private(set) lazy var goalView = AnyConfigurable(button) private(set) lazy var successImageView = AnyConfigurable(imageView) private var label = UILabel() private var button = UIButton() private var imageView = UIImageView() // ViewController implementation }
  • 34.
    34// Read More Great articlefrom bigNerdRanch that inspired this talk https://www.bignerdranch.com/blog/breaking-down-type-erasures-in-swift/ Swift Generic Manifesto https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md My github with a playground related to this talk https://github.com/denisPoifol/Talks/
  • 35.
    Merci. 35// Denis Poifol Junior SoftwareEngineer +33 6 58 90 85 54 denis.poifol@fabernovel.com