[PITCH] Hint to owners of protocol functions

Problem:

Which function is being called by the delegate BLEManagerDelegate?

class DeviceManager: BLEManagerDelegate {
        func didConnect(with device: BLEDevice, at timestamp: UInt64) {
            //..
        }
        
        func didConnect(device: BLEDevice) {
          //..
        }

        func didDisconnect(device: BLEDevice) {
            //..
        }
    }

Apple's solution:

class DeviceManager: BLEManagerDelegate {
        func didConnect(with device: BLEDevice, at timestamp: UInt64) {
            //..
        }
        
        func bleManager(didConnect device: BLEDevice) {
            //..
        }
        
        func bleManager(didDisconnect device: BLEDevice) {
            //..
        }
    }

In Apple's solution, the function name becomes an identifier of the delegate's owner. The function action becomes a parameter label. Large amount of protocol functions end up with the same name and difficult to read parameter labels. You can see this pattern for example in the CBCentralManagerDelegate

Proposed solution:

class DeviceManager: BLEManagerDelegate {
    func didConnect(with device: BLEDevice, at timestamp: UInt64) {
        //..
    }

    func BLEManagerDelegate.didDisconnect(device: BLEDevice) {
        //..
    }

    func BLEManagerDelegate.didConnect(device: BLEDevice) {
        //..
    }
}

In the proposed solution, the function owner is hinted (optionally) before the function name.
Another syntax could be possible, for example a C style double colon (::), as the dot notation is typically used for function calls, not declarations.

1 Like

As someone present during the initial Swift 1 bring-up, let me say we never came up with a satisfactory naming convention for delegate methods, and consoled ourselves that Objective-C also didn’t have a good answer for delegate methods with only the object as a parameter. But to be clear, the Apple convention for multiple parameters is object(_:didAThingTo:), not object(with:didAThingTo:). The convention for single parameters is objectDidAThing(_:). Your examples should have a BLEManager as a parameter somewhere.

6 Likes

This is one of those times where I mildly wish we could have a "labeled non-argument". Then you could write something like delegate.object(obj, didAThing:), and it would align cleanly with the other methods. The closest we can get today is making it a Void argument, which I think is strictly worse since you have to pass the () in there.

But I doubt doing this would be worth the additional complexity (both technically and mentally), because modern Swift code in many cases tries to drive users toward patterns other than delegates anyway.

4 Likes

Still, the essential point stays the same - a function gets a name of an object. Not so Swifty.

2 Likes

I am sorry, but the question sounds a bit confusing.

Do you mean which function of the BLEManagerDelegate is called?

Maybe I am reading it to literally. My understanding is that an instance of a DeviceManager would be passed along as a delegate to some BLEManager, which would then call the delegate's functions.

Yes, I found it confusing too. I think what @nikodiyana means is: (rhetorically) which of these methods (on DeviceManager) are implementing the BLEManagerDelegate protocol and which are just random other methods that might be confusingly similar in name.

But as @jrose note's, the Cocoa convention for delegate (and datasource) methods is that the object using the delegate passes itself as the first parameter (some BLEManager in this case, presumably). With that in place it's usually pretty clear which methods are delegate protocol implementations and which are not.

(the reason for that convention is that an object instance can be the delegate for multiple things - e.g. a view controller or data source that handles multiple views - so it needs a way to know which of those things is talking to it at any given time)

2 Likes

To be clear (again), I appreciate looking for a different answer, because right now delegate methods read differently from the usual API guidelines and it does in fact make it a little harder to understand—it’s an extra thing you have to learn. Something like the original proposal here would also be an additional thing you have to learn, though, so only if it’s a clear improvement would it be worth it, especially after almost ten years of Swift.

FWIW, having used the existing pattern for about a decade of Objective-C development, I found it works well. It reads cleanly (more-so in Objective-C than in Swift, but it's close) and I don't recall ever being confused about which methods were associated with delegate protocols.

Especially, since Swift has the ability to add protocol conformance via extensions, it's good practice to separate protocol implementations into their own blocks anyway. Which doesn't necessarily solve the problem if that structure is lost - e.g. in documentation viewers - but that can be addressed by either suitably preserving the structure or (more simply) annotating methods & properties in the documentation with which protocol they come from.

I'm not opposed to something like the proposed new syntax, iff it's optional - I definitely don't want to have to write all that extra boilerplate in the common case. It's pretty similar to the existing module disambiguation syntax, which is similarly useful in some niche cases but thankfully not most of the time.

@allevato:

modern Swift code in many cases tries to drive users toward patterns other than delegates

I am surprised by this and I like it. Swift indeed has powerful closures to make delegate classes obsolete. Definitely something to try in the future.

Otherwise, for existing code @wadetregaskis put it so right:

an object instance can be the delegate for multiple things - e.g. a view controller or data source that handles multiple views - so it needs a way to know which of those things is talking to it at any given time

This is where the proposed solution would help for both programmers writing and programmers reading such pattern, without forcing function names to take object names.

If not a language implementation, last hope would be just an IDE implemented hint, which can provide some clarity.