Idiomatic Swift multiplex notifications?

北城以北 提交于 2019-12-13 01:50:42

问题


I'm defining a class structure that needs to notify its consumers when interesting things happen to it (gets new data, and so on). Similar to a delegate relationship, but there may be many consumers. And a more direct relationship than casting an NSNotification into the wind.

My inclination in most Objective-C programs would just be to define a protocol, and let those consumers implement it and register themselves as id<MyProtocol>; I'd stuff them into an NSMutableSet, which I'd iterate over at opportune moments. My time spent writing C# would incline me to try to modify that approach somewhat to use generics in Swift (a la private var myConsumers = Set<MyProtocol>()). That turns out to be a dark and painful rabbit hole, as far as I can tell. But rather than dive into it, let me back up and try to solve the real problem.

So: I have some class where instances will need to notify 0-N consumers of interesting things that happen. It'd be nice to allow those consumers to register and de-register themselves, since their lifespans may be shorter than my class.

What is the idiomatic Swift approach to implementing this pattern?


回答1:


The idiomatic solution to multi-delegations in Cocoa is notifications, specifically using addObserverForName(object:queue:usingBlock:). Pass your closure. Save the return value, and use that return value to unregister yourself in the future.

There are ObjC multicast delegates (Robbie Hanson's is probably my favorite outside of the ones I've implemented), but most of the time its more trouble than its worth. Notifications are how Cocoa manages non-delegate observers.

"Non-delegate" here means "things that are notified, but never asked anything." The problem with "multi-delegate" is that it is really a nonsense term. A "delegate" is something you ask for information; something you "delegate" responsibility for a decision to. Observers aren't really "delegates" (though delegates are sometimes observers). You don't ask them anything. They have no control over decision making. So a multi-delegate is nonsense. How can multiple things all be responsible for answering a question? To make this more concrete, delegates have non void methods. Consider what it would mean to have a "multi-delegate" for UITableViewDelegate. How would you decide what the row height was?

If you are building your own, I would probably recommend Dictionary<Key, ()->Void> where "key" is the handle you return to the caller for later removal. That way you don't have to worry about the craziness of closure equality (which I agree with Chris Lattner is a very open-ended, and possibly unsolvable, question).




回答2:


Here's the most minimal thing I've been able to come up with so far, to emulate MultiCastDelegate.

struct MultiClosure<T, U> {
    private typealias Key = MultiClosureKey
    private let closures: [Key: T -> U]

    init() {closures = Dictionary<Key, T -> U>()}
    init(_ closure: T -> U) {closures = [Key(): closure]}

    private init(_ closures: [Key: T -> U]) {self.closures = closures}

    func run(t: T) {
        for closure in closures.values {closure(t)}
    }
}


private class MultiClosureKey: Hashable {
    // Is this ignorant? I don't know yet.
    // It seems to work so I haven't looked for a better solution.
    var hashValue : Int { return ObjectIdentifier(self).hashValue }
}

private func == (lhs: MultiClosureKey, rhs: MultiClosureKey) -> Bool {
    return lhs === rhs
}



func + <T, U>(left: MultiClosure<T, U>, right: MultiClosure<T, U>) -> MultiClosure<T, U> {
    return MultiClosure(left.closures + right.closures)
}

func += <T, U>(inout left: MultiClosure<T, U>, right: MultiClosure<T, U>) {
    left = left + right
}

func - <T, U>(left: MultiClosure<T, U>, right: MultiClosure<T, U>) -> MultiClosure<T, U> {
    return MultiClosure(left.closures - right.closures)
}

func -= <T, U>(inout left: MultiClosure<T, U>, right: MultiClosure<T, U>) {
    left = left - right
}

Dictionary extensions:

func + <T, U>(var left: Dictionary<T, U>, right: Dictionary<T, U>) -> Dictionary<T, U> {
    for (key, value) in right {
        left[key] = value
    }
    return left
}

func += <T, U>(inout left: Dictionary<T, U>, right: Dictionary<T, U>) {
    left = left + right
}

func - <T, U>(var left: Dictionary<T, U>, right: Dictionary<T, U>) -> Dictionary<T, U> {
    for key in right.keys {
        left[key] = nil
    }
    return left
}

func -= <T, U>(inout left: Dictionary<T, U>, right: Dictionary<T, U>) {
    left = left - right
}



回答3:


I wrote a native Swift notification broadcasting system which enables developers to match a specific notification type by using switch-case syntax and works the same to NSNotificationCenter.

I'm now handling notifications like:

func handleNotification(notification: PrimitiveNotificationType) {  
    switch notification {
    case let aNotification as CustomNotification:
        let initialFrame = aNotification.initialFrame
        let finalFrame = aNotification.finalFrame
        print("Initial frame: \(initialFrame)")
        print("Final frame: \(finalFrame)")
    default: return
    }
}

Here is the post: https://wezzard.com/2015/08/08/notification-handling-best-practice-in-swift/

Here is the code: https://github.com/WeZZard/Nest

Plus, these stuffs works only on Swift 2.



来源:https://stackoverflow.com/questions/28503875/idiomatic-swift-multiplex-notifications

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!