Swift: workaround for closure as protocol
In Java, it was common to implement callbacks as anonymous classes. It was nice to be able to define the callback right where you define the button.
Here is a simple JButton ActionListener example:
1 2 3 4 5 6 |
JButton button = new JButton("Press me"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("You clicked the button"); } }); |
Wouldn’t it be cool to do the same thing in Swift?
Let’s create a Swift protocol. Just a single method that takes an array of Strings.
1 2 3 |
protocol SimpleProtocol { func didReceiveResults(s: [String]) -> Void } |
Now a method in that uses the protocol.
1 2 3 4 |
func frob(s:String, delegate: SimpleProtocol) { println(s) delegate.didReceiveResults(["foo", "bar"]) } |
Straightforward stuff so far.
Now let’s try calling our method using a trailing closure for the delegate.
1 2 3 |
frob("hi") { (a:[String]) in } |
Blammo. Can’t do it.
How about leaving out the params like this?
1 2 3 |
frob("hi") { // use $0 for the params } |
Nope. I am disappoint. I wish I could do either of these. So what can we do?
I guess we need a named class the implements the protocol.
1 2 3 4 5 6 7 8 |
class NonAnon:SimpleProtocol { func didReceiveResults(s: [String]) -> Void { println(s) for str in s { println(str) } } } |
Then
1 2 3 4 5 |
var resp:NonAnon = NonAnon() self.frob("handler", delegate:resp) // or typed to the protocol var handler:SimpleProtocol = resp self.frob("handler", delegate:handler) |
Yeah, ok. What if you want to call a method from the calling class?
One way is to pass in the calling class (in this case that is ViewController which has a method named blob()).
1 2 3 4 5 6 7 8 9 10 |
class NonAnonDelegate:SimpleProtocol { var vc:ViewController? init(c:ViewController) { self.vc = c } func didReceiveResults(s: [String]) -> Void { vc?.blob() } } |
In a Java anonymous inner class you have access to the methods and variables of the outer class. Will that work?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class ViewController { blah blah func blob() {} class NonAnonDelegate:SimpleProtocol { var vc:ViewController? init(c:ViewController) { self.vc = c } func didReceiveResults(s: [String]) -> Void { // ok vc?.blob() // blows up XCode blob() } } } |
Nope. The Swift compiler barfs on its shoes and the XCode editor goes into convulsions.
So, yes, it’s ugly. I’ll be using the NonAnonDelegate:SimpleProtocol version. Swift nested classes don’t have access to the outer class, but you still are able to define them near where they are needed.
Unless you know a better way. Please let me know.
Download the project from this Github repository.
I found another way. So for example say I have an interface like (note this is Kotlin not Java)
interface OnConnectionCallback {
fun onConnected(device:WirelessLezyneDevice)
fun onConnectionFailed()
}
and I use it like
bluetoothService?.connect(object: OnConnectionCallback {
override fun onConnectionFailed() { }
override fun onConnected(device: WirelessLezyneDevice) { } ))
I want the same sort of thing in Swift so I used a class with member variable closures
class OnConnectionCallback {
let onConnected: (WirelessLezyneDevice)->()
let onConnectionFailed: ()->()
init(onConnected: @escaping (WirelessLezyneDevice)->(), onConnectionFailed: @escaping ()->()) {
self.onConnected = onConnected
self.onConnectionFailed = onConnectionFailed
}
}
and then when I use it the syntax is nice like so
bluetoothService.connect(scanResult: scanResult, onConnection: OnConnectionCallback(
onConnected: { (device) in },
onConnectionFailed: { }))