Deferring and Delegating in Swift 2 (Xcode 7 beta 3) - updated


We're used to the Cocoa framework having classes that are assigned delegates from actions such as XML parsing. But what if we want to create our own types with our own delegates.
protocol Main {
    var delegate:Delegate {get set}
}

protocol Delegate {
    func started()
    func finished()
}
The above code defines a simple delegate relationship using protocols. There then follows two types that fulfil these protocols:
struct MyMain: Main {
    var delegate:Delegate
    func doSomething() {
        delegate.started()
        print(1+1)
        delegate.finished()
    }
}

struct MyDelegate: Delegate {
    func started() {
        print("it's started")
    }
    func finished() {
        print("it's finished")
    }
}
As long as the main type is assigned a delegate it fulfils the protocol but the delegate itself must have started() and finished() methods because they are outlined in the delegate protocol. The doSomething() method in the main type could be replaced with anything and there is no requirement to call any delegate methods at all from the main type. We simply choose to in this instance.

To implement this simply use the following code:
let d = MyDelegate()
let m = MyMain(delegate: d)
m.doSomething()

Defer

Let's suppose things are a bit more complicated than the doSomething() method:
struct MyMain: Main {
    var delegate:Delegate
    func isItTheWeekend(date:NSDate) throws -> String {
        delegate.started()
        
        defer {
            delegate.finished()
        }
        
        let flags: NSCalendarUnit = [.Weekday]
        
        guard let gregorian = NSCalendar(calendarIdentifier:NSCalendarIdentifierGregorian) else {
            throw Error.NoSuchCalendar
        }
        
        let dateComponents = gregorian.components(flags, fromDate: date)
        switch dateComponents.weekday {
        case 2...6:
            return "No, it's a weekday"
        default:
            return "Yes, it's the weekend"
        }
    }
}
This method is far more complicated, it tells us based on a date whether it's a weekday or the weekend. It also throws an error if it can't find the calendar it needs. We'll approach the code for the error handling in a moment, but first you'll notice that instead of writing delegate.finished() after every possible outcome what has happened instead is that the defer keyword has been used so that whatever happens we'll always know that the method has finished. Take a moment to admire this. Then take a moment to admire the use of guard.

Error

Now here's the Error type code:
code:enum Error: ErrorType {
    case NoSuchCalendar
}
It's unlikely that we'll actually have a throw in real use here so I'm going to let the app crash if an error is thrown by implementing the method in the following way:
let d = MyDelegate()
let m = MyMain(delegate: d)
let date = NSDate()
try! m.isItTheWeekend(date)

Mixing things up

If we didn't want the method identifying itself as having finished when an error is thrown we could switch things around like this:
struct MyMain: Main {
    var delegate:Delegate
    func isItTheWeekend(date:NSDate) throws -> String {
        delegate.started()
        
        let flags: NSCalendarUnit = [.Weekday]
        
        guard let gregorian = NSCalendar(calendarIdentifier:"my cal") else {
            defer {delegate.finishedWithError()}
            throw MyError.NoSuchCalendar
            
        }
        defer {
            delegate.finished()
        }
        
        
        let dateComponents = gregorian.components(flags, fromDate: date)
        
        switch dateComponents.weekday {
        case 2...6:
            return "No, it's a weekday"
            
        default:
            return "Yes, it's the weekend"
        }
    }
}
You'll notice I've added an additional method at the same time that needs to be in our protocol and delegate called finishedWithError(), and that I've deferred this so that it happens immediately after the error rather than before. (We couldn't achieve this at all without defer.)

The delegate now looks like this:
struct MyDelegate: Delegate {
    func started() {
        print("it's started")
    }
    func finished() {
        print("it's finished")
    }
    func finishedWithError() {
        print("finished with error")
    }
}
And the delegate protocol looks like this:
protocol Delegate {
    func started()
    func finished()
    func finishedWithError()
}
There's a lot of stuff that is new to Swift 2 and Xcode 7 in the code I've written, which I won't discuss at length here, but if you search earlier blogposts on this site (and elsewhere) and refer to Apple docs and WWDC 2015 videos then you'll soon be up to speed.

Optional methods

Often when working with Cocoa there are required delegate methods and there are optional ones. We can make methods optional in Swift too, but the protocol must be prefixed by @objc (to make it available in the Objective-C runtime) and this means a type based on the protocol must use a class. So let's look at what the entire code looks like if we wanted to make one of the methods optional:
protocol Main {
    var delegate:Delegate {get set}
}

@objc protocol Delegate {
    optional func started()
    func finished()
    func finishedWithError()
}

enum MyError: ErrorType {
    case NoSuchCalendar
}
struct MyMain: Main {
    var delegate:Delegate
    func isItTheWeekend(date:NSDate) throws -> String {
        delegate.started?()
        
        let flags: NSCalendarUnit = [.Weekday]
        
        guard let gregorian = NSCalendar(calendarIdentifier:"my cal") else {
            defer {delegate.finishedWithError()}
            throw MyError.NoSuchCalendar
            
        }
        defer {
            delegate.finished()
        }
        
        
        let dateComponents = gregorian.components(flags, fromDate: date)
        
        switch dateComponents.weekday {
        case 2...6:
            return "No, it's a weekday"
            
        default:
            return "Yes, it's the weekend"
        }
    }
}
@objc class MyDelegate: Delegate {
    func finished() {
        print("it's finished")
    }
    func finishedWithError() {
        print("finished with error")
    }
}
let d = MyDelegate()
let m = MyMain(delegate: d)
let date = NSDate()

do {
    try m.isItTheWeekend(date)
}
catch MyError.NoSuchCalendar {
    print("error thrown")
}
Here delegate.started?() returns nil because there is no started() method in our delegate type. This is not a problem, our main type isn't effected by the delegate's ignorance, we simply aren't told within the delegate when our main type (which remains a struct) has started. But we are told when it has finished. This is our own choice, and if we were to add a started() method then we would be notified.

Note: because the whole MyDelegate class is marked @objc there's no need to mark methods @objc but had we not marked class as @objc then both methods would need marking @objc.

Weakness

This raises an issue, as succinctly pointed out by Marcin Krzyzanowski, because if we're now using a class for the delegate then we risk a strong reference cycle if the instance we create maintains a reference to its delegate. We therefore typically want to make the reference a weak one. This requires a little bit of hoop jumping. First, if the delegate is going to be weakly referenced it also needs to be optional (which is not a bad thing because delegates often are optional):
weak var delegate:Delegate?
Second, the compiler won't allow us to make the delegate weak unless the protocol to which the delegate confirms is a class-only one, and we need to specify this using the class keyword.
@objc protocol Delegate: class {
    optional func started()
    func finished()
    func finishedWithError()
}
Third, the protocol for our main type also needs to specify that the delegate should be optional:
protocol Main {
    var delegate:Delegate? {get set}
}
Although it does not have to specify weak unless we wish this to always be the case.

Conclusion

For completeness, without the optional method (but making the delegate optional) the entire code looks like this:
protocol Main {
    var delegate:Delegate? {get set}
}

protocol Delegate {
    func started()
    func finished()
    func finishedWithError()
}

enum MyError: ErrorType {
    case NoSuchCalendar
}
struct MyMain: Main {
    var delegate:Delegate?
    func isItTheWeekend(date:NSDate) throws -> String {
        delegate?.started()
        
        let flags: NSCalendarUnit = [.Weekday]
        
        guard let gregorian = NSCalendar(calendarIdentifier:"my cal") else {
            defer {delegate?.finishedWithError()}
            throw MyError.NoSuchCalendar
            
        }
        defer {
            delegate?.finished()
        }
        
        
        let dateComponents = gregorian.components(flags, fromDate: date)
        
        switch dateComponents.weekday {
        case 2...6:
            return "No, it's a weekday"
            
        default:
            return "Yes, it's the weekend"
        }
    }
}
struct MyDelegate: Delegate {
    func started() {
        print("it's started")
    }
    func finished() {
        print("it's finished")
    }
    func finishedWithError() {
        print("finished with error")
    }
}
let d = MyDelegate()
let m = MyMain(delegate: d)
let date = NSDate()

do {
    try m.isItTheWeekend(date)
}
catch MyError.NoSuchCalendar {
    print("error thrown")
}
And with the optional methods and weak delegate reference it looks like this:
protocol Main {
    var delegate:Delegate? {get set}
}

@objc protocol Delegate: class {
    optional func started()
    func finished()
    func finishedWithError()
}

enum MyError: ErrorType {
    case NoSuchCalendar
}
struct MyMain: Main {
    weak var delegate:Delegate?
    func isItTheWeekend(date:NSDate) throws -> String {
        delegate?.started?()
        
        let flags: NSCalendarUnit = [.Weekday]
        
        guard let gregorian = NSCalendar(calendarIdentifier:NSCalendarIdentifierGregorian) else {
            defer {delegate?.finishedWithError()}
            throw MyError.NoSuchCalendar
            
        }
        defer {
            delegate?.finished()
        }
        
        
        let dateComponents = gregorian.components(flags, fromDate: date)
        
        switch dateComponents.weekday {
        case 2...6:
            return "No, it's a weekday"
            
        default:
            return "Yes, it's the weekend"
        }
    }
}

@objc class MyDelegate: Delegate {
    
    func finished() {
        print("it's finished")
    }
    func finishedWithError() {
        print("finished with error")
    }
}
let d = MyDelegate()
let m = MyMain(delegate: d)
let date = NSDate()

do {
    try m.isItTheWeekend(date)
}
catch MyError.NoSuchCalendar {
    print("error thrown")
}
I've shamelessly borrowed the idea of using the defer keyword in conjunction with a delegate protocol from the WWDC 2015 videos and think it an ideal scenario for the keyword but would be interested to see other uses that it might be put to. If you have any, please share and link to your examples.

Afterword

Following the original posting, József Vesza shared his approach which is to use multiple protocols in preference to optional methods and the @objc keyword. (He was guided in this by Ash Furrow's post.)

I also found this take on defer on StackOverflow where the reply offers defer to replace try-catch-finally as found in C#. Although it is rather problematic because defer will wait until everything else in the scope has finished. This means that there might well be a need to seal the try-catch-defer trio inside a nested function or method for it to exactly replicate the try-catch-finally behaviour. Unless anyone else has a clever twist to add here.

And finally thanks to Marcin Krzyzanowski for pointing out the weakness I was lacking in linking to a delegate that is a class type.

Endorse on Coderwall

Comments