Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-06-04

Swift Code Library: Merged Notifications

I don't know about you, but I often find that I need to stop sending a notification for a certain duration. This mostly happens when an array of visible objects is being updated. For example, suppose there is an array of strings that are visible in a view. When a string is updated, a notification is send to the view to update itself. But when the entire array must be updated, it is not helpful to send a notification for each string, instead I would want to send only a single notification at the end.

Besides, sending a notification without parameters is a bit ugly in Objective-C, in Swift this can be done much nicer by encapsulating the notification in an object of its own.

Both the above issues have lead me to implement the following "MergeableNotification" class:

struct MergeableNotification {
    
    private var suspendLevel = 0
    
    private var notificationNeeded = false
      
    private var notificationName: String
    private unowned var notificationSourceObject: AnyObject
        
    mutating func post() {
        
        if suspendLevel == 0 {
            
            NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: notificationSourceObject)
        
        } else {
        
            notificationNeeded = true
        }
    }
        
    mutating func merge(closure: () -> () ) {
        suspendLevel++
        closure()
        suspendLevel--
        if suspendLevel == 0 && notificationNeeded {
            NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: notificationSourceObject)
            notificationNeeded = false
        }
    }
        
    init(name: String, sourceObject: AnyObject) {
        self.notificationName = name
        self.notificationSourceObject = sourceObject
    }

}


This class is used as follows:

Defining a filtered notification


let LINE_UPDATED_NOTIFICATION = "LineUpdatedNotification"

class DataModel {

    var lineUpdatedNotification: MergeableNotification!
    
    init() {
        lineUpdatedNotification = MergeableNotification(name: LINE_UPDATED_NOTIFICATION, sourceObject: self)
    }

    ... more
}

If the object sending the notification is self, then we have to define the notification as forced unwrapped and initialise it in the initialiser.

Posting a notification


This is easy:

    func removeCharacterAt(index: Int) {

        ... more

        lineUpdatedNotification.post()
    }

Merging notifications to prevent a notification storm


This may be a bit unusual if you (like me) are not used to functional programming. The code that can raise notifications is called from within a closure. All the notifications that would be raised in the closure are merged into a single notification that will be fired when the closure end, IF that closure is the top-level closure. I.e. if the call to "merge" is nested, then the inner closures will NOT fire a notification when they terminate.

func moveToLeftEndOfLine() {
    
    lineUpdatedNotification.merge(
        {
            [unowned self] in
            
            self.removeInsertionPoints()
            self.removeSelections()
            self.addInsertionPoint(self.line.startIndex)
        }
    )
}

Note that this implementation is not thread safe. But as long as the calls to "merge" are made from within the same thread, even when nested, this should work fine.

Initially I wanted to use a push-pop approach for the nesting of calls to "merge". But that approach has two distinct problems:
1) push and pop are two calls, forget one and you are in trouble. But that trouble could hide itself until the app was in the user's hands.
2) push and pop would need to be called from the same ident level in the code as the code in between. That makes it hard to spot where to start and end the merging.
Using a closure neatly avoids those two problems. And though it necessitates the need to quote 'self' I find that less troubling that the two above problems.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment