How to make generic extension with associatedtype protocol in Swift

Issue #104

I like extensions, and I like to group them under 1 common property to easily access. This also makes it clear that these all belong to the same feature and not to confuse with Apple properties.

This is how I do it in Anchor and On

1
2
3
4
5
6
activate(
a.anchor.top.left,
b.anchor.top.right,
c.anchor.bottom.left,
d.anchor.bottom.right
)
1
2
3
4
5
6
7
textField.on.text { text in
print("textField text has changed")
}

textField.on.didEndEditing { text in
print("texField has ended editing")
}

Generic extension

For On, it is a bit tricky as it needs to adapt to different NSObject subclasses. And to make auto completion work, meaning that each type of subclass gets its own function hint, we need to use generic and associatedtype protocol.

You can take a look at Container and OnAware

1
2
3
4
5
6
7
public class Container<Host: AnyObject>: NSObject {
unowned let host: Host

init(host: Host) {
self.host = host
}
}
1
2
3
4
5
public protocol OnAware: class {
associatedtype OnAwareHostType: AnyObject

var on: Container<OnAwareHostType> { get }
}

RxCocoa

RxSwift has its RxCocoa that does this trick too, so that you can just declare

1
2
3
button.rx.tap
textField.rx.text
alertAction.rx.isEnabled

The power lies in the struct Reactive and ReactiveCompatible protocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public struct Reactive<Base> {
/// Base object to extend.
public let base: Base

/// Creates extensions with base object.
///
/// - parameter base: Base object.
public init(_ base: Base) {
self.base = base
}
}

public protocol ReactiveCompatible {
/// Extended type
associatedtype CompatibleType

/// Reactive extensions.
static var rx: Reactive<CompatibleType>.Type { get set }

/// Reactive extensions.
var rx: Reactive<CompatibleType> { get set }
}

extension ReactiveCompatible {
/// Reactive extensions.
public static var rx: Reactive<Self>.Type {
get {
return Reactive<Self>.self
}
set {
// this enables using Reactive to "mutate" base type
}
}

/// Reactive extensions.
public var rx: Reactive<Self> {
get {
return Reactive(self)
}
set {
// this enables using Reactive to "mutate" base object
}
}
}

Here UIButton+Rx you can see how it can be applied to UIButton

1
2
3
4
5
6
7
extension Reactive where Base: UIButton {

/// Reactive wrapper for `TouchUpInside` control event.
public var tap: ControlEvent<Void> {
return controlEvent(.touchUpInside)
}
}

Comments