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

2015-03-07

Replacing "performSelector" with a closure

In some code that I am porting to Swift I used the operation "performSelector". This is no longer available and has to be replaced by a closure. In this article I want to present an example of how I did this.

In Objective-C I had a piece of code that would call an operation on an array of objects that contained strings. Each string-containing object would report back if a change occurred. And each object that reported a change had to be reported in a notification.

This is the Objective-C code:

-(void)forAllObjectsDo:(SEL)method {

    // The line numbers that are send with the notification (if any)
    NSMutableArray* arr = [[NSMutableArray alloc] init];
    
    // For all objects do
    for (int i=0; i<[datamodel count]; i++) {
        
        // Get the object to process
        BRObject* obj = [datamodel objAtIndex:i];
        
        // Does the object respond to the given selector?
        if ([obj respondsToSelector:method]) {
            
            // Supress the "leak" warning, we don't want unneccessary warnings.
            #pragma clang diagnostic push
            #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            
            // The object implements the selector, call the selector and
            // check if the object was changed
            if ([obj performSelector:method]) {
                
                // Something changed.
                // Add this line to de notification info.
                [arr addObject:[NSNumber numberWithInt:i]];
            }
            
            // Restore the old diagnostics
            #pragma clang diagnostic pop
            
        }
    }
    
    // If there are changes, post the notification
    if ([arr count] > 0) {

        // Generate and send a notification
        NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys:arr, UPDATED_OBJECTS_KEY, nil];
        [[NSNotificationCenter defaultCenter] postNotificationName:OBJECTS_UPDATED_NOTIFICATION
                                                            object:self
                                                          userInfo:dict];
    }

}

Even though I had used closures before, I still was at a loss how to convert the above code until I had some inspiration earlier this week. This is what I came up with:

    private func forAllObjectsDo(closure: (Int) -> Bool) {
        var arr: Array<Int> = []
        for i in 0 ..< dataModel.objects.count {
            if closure(i) { arr.append(i) }
        }
        fireViewUpdatedNotificationIfNotEmpty(arr)
    }
    
    private func fireViewUpdatedNotificationIfNotEmpty(arr: Array<Int>) {
        if arr.count > 0 {
            let dict = [UPDATED_OBJECTS_KEY: arr]
            NSNotificationCenter.defaultCenter().postNotificationName(OBJECT_UPDATED_NOTIFICATION, object:self, userInfo:dict)
        }

    }

The Objective-C routine was called as follows:

[self forAllObjectsDo:@selector(deleteHalf)];

The Swift code is called like this:

forAllObjectsDo({ [unowned self] (i) in return self.datamodel.objAtIndex(i).deleteHalf() })

All in al the new way is much better looking than the old way. And once I grokked the usage of closures, it seems silly that it was so hard to do before. The key was to realise that the old code cannot be replaced 1 to 1 with new code. As you can see above the separation is slightly different. In Objective-C I could iterate over the objects and call the selector, in the Swift code I have to include the selection of the object to call into the closure.

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