Search code examples
iosswiftsingletonmessagingswift-protocols

In Swift, register "anywhere" to be a delegate of a protocol


I have a complicated view class,

class Snap:UIViewController, UIScrollViewDelegate
   {
   }

and the end result is the user can, let's say, pick a color...

protocol SnapProtocol:class
    {
    func colorPicked(i:Int)
    }
class Snap:UIViewController, UIScrollViewDelegate
   {
   someDelegate.colorPicked(blah)
   }

So who's going to handle it.

Let's say that you know for sure there is something up the responder chain, even walking through container views, which is a SnapProtocol. If so you can use this lovely code to call it

var r : UIResponder = self
repeat { r = r.nextResponder()! } while !(r is SnapProtocol)
(r as! SnapProtocol).colorPicked(x)

If you prefer, you can use this superlative extension

public extension UIResponder        // walk up responder chain
    {
    public func next<T>() -> T?
        {
        guard let responder = self.nextResponder()
            else { return nil }
        return (responder as? T) ?? responder.next()
        }
    }

courtesy these guys and safely find any SnapProtocol above you,

 (next() as SnapProtocol?)?.colorPicked(x)

That's great.

But. What if the object that wants to get the colorPicked is a knight-move away from you, down some complicated side chain, and/or you don't even know which object wants it.

My current solution is this, I have a singleton "game manager" -like class,

public class .. a singleton
    {

    // anyone who wants a SnapProtocol:
    var useSnap:SnapProtocol! = nil

    }

Some freaky class, anywhere, wants to eat SnapProtocol ...

class Dinosaur:NSObject, SnapProtocol
    {
    ....
    func colorPicked(index: Int)
        {...}

... so, to set that as the desired delegate, use the singleton

 thatSingleton.useSnap = dinosaur

Obviously enough, this works fine.

Note too that I could easily write a little system in the singleton, so that any number of users of the protocol could dynamically register/deregister there and get the calls.

But it has obvious problems, it's not very "pattern" and seem violently non-idiomatic.

So. Am I really, doing this the right way in the Swift milieu?

Have I indeed confused myself, and there is some entirely different pattern I should be using in today's iOS, to send out such "messages to anyone who wants them?" ... maybe I shouldn't even be using a protocol?


Solution

  • There is no "protocol based" notification mechanism in the Swift standard libraries or runtime. A nice implementation can be found here https://github.com/100mango/SwiftNotificationCenter. From the README:

    A Protocol-Oriented NotificationCenter which is type safe, thread safe and with memory safety.

    • Type Safe

      No more userInfo dictionary and Downcasting, just deliver the concrete type value to the observer.

    • Thread Safe

      You can register, notify, unregister in any thread without crash and data corruption.

    • Memory Safety

      SwiftNotificationCenter store the observer as a zeroing-weak reference. No crash and no need to unregister manually.

    It's simple, safe, lightweight and easy to use for one-to-many communication.

    Using SwiftNotificationCenter, a (instance of a) conforming class could register itself for example like this:

    class MyObserver: SnapProtocol {
        func colorPicked(i: Int) {
            print("color picked:", i)
        }
        init() {
            NotificationCenter.register(SnapProtocol.self, observer: self)
        }
    }
    

    and a broadcast notification to all conforming registered observers is done as

    NotificationCenter.notify(SnapProtocol.self) {
        $0.colorPicked(x)
    }