No more [weak self], or the weird new future of delegation

Ready for some property wrappers?

final class TextField {
var didUpdate: (String) -> () = { _ in }

/// ...

private func didFinishEditing() {
didUpdate(self.text)
}
}
final class ViewController {
let label = Label()
let textField = TextField()

func viewDidLoad() {
textField.didUpdate = { text in
self.label.text = text
}
}
}
func viewDidLoad() {
textField.didUpdate = { [weak self] text in
self.label.text = text
}
}
struct Delegated<Input> {

private(set) var callback: ((Input) -> Void)?

mutating func delegate<Object : AnyObject>(
to object: Object,
with callback: @escaping (Object, Input) -> Void
){
self.callback = { [weak object] input in
guard let object = object else {
return
}
callback(object, input)
}

}

}
final class TextField {
var didUpdate = Delegated<String>()

/// ...

private func didFinishEditing() {
didUpdate.callback?(self.text)
}
}
final class ViewController {
let label = Label()
let textField = TextField()

func viewDidLoad() {
textField.didUpdate.delegate(to: self) { (self, text) in
self.label.text = text
}
}
}
@propertyWrapper
public final class Delegated<Input> {

public init() {
self.callback = { _ in }
}

private var callback: (Input) -> Void

public var wrappedValue: (Input) -> Void {
return callback
}

public var projectedValue: Delegated<Input> {
return self
}

func delegate<Target: AnyObject>(
to target: Target,
with callback: @escaping (Target, Input) -> Void
) {
self.callback = { [weak target] input in
guard let target = target else {
return
}
return callback(target, input)
}
}
}
final class TextField {
@Delegated var didUpdate: (String) -> Void

/// ...

private func didFinishEditing() {
self.didUpdate(self.text)
}
}
final class ViewController {
let label = Label()
let textField = TextField()

func viewDidLoad() {
textField.$didUpdate.delegate(to: self) { (self, text) in
self.label.text = text
}
}
}

Caveats

final class Button {
@Delegated var didPress: () -> Void
}
final class ScrollView {
@Delegated var didScrollTo: (_ x: CGFloat, _ y: CGFloat) -> Void
}
final class Button {
@Delegated var didPress: (()) -> Void
}
final class ScrollView {
@Delegated var didScrollTo: ((x: CGFloat, y: CGFloat)) -> Void
}
@propertyWrapper
public final class Delegated0 {

public init() {
self.callback = { }
}

private var callback: () -> Void

public var wrappedValue: () -> Void {
return callback
}

public var projectedValue: Delegated0 {
return self
}

func delegate<Target: AnyObject>(
to target: Target,
with callback: @escaping (Target) -> Void
) {
self.callback = { [weak target] in
guard let target = target else {
return
}
return callback(target)
}
}
}
final class Button {
@Delegated0 var didPress: () -> Void
}
final class ScrollView {
@Delegated2 var didScrollTo: (_ x: CGFloat, _ y: CGFloat) -> ()
}

Further learning:

iOS development know-it-all, Co-founder at nicephoton.com. Talk to me about Swift, coffee, photography & motorsports.

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