Search code examples
iosswiftuiviewcontrollerdelegatesnsnotificationcenter

Concern about memory when choosing between notification vs callback closure for network calls?


Many posts seem to advise against notifications when trying to synchronize functions, but there are also other posts which caution against closure callbacks because of the potential to inadvertently retain objects and cause memory issues.

Assume inside a custom view controller is a function, foo, that uses the Bar class to get data from the server.

class CustomViewController : UIViewController {

    function foo() {
       // Do other stuff
       // Use Bar to get data from server
       Bar.getServerData()
     }

}

Option 1: Define getServerData to accept a callback. Define the callback as a closure inside CustomViewController.

Option 2: Use NSNotifications instead of a callback. Inside of getServerData, post a NSNotification when the server returns data, and ensure CustomViewController is registered for the notification.

Option 1 seems desirable for all the reasons people caution against NSNotification (e.g., compiler checks, traceability), but doesn't using a callback create a potential issue where CustomViewController is unnecessarily retained and therefore potentially creating memory issues?

If so, is the right way to mitigate the risk by using a callback, but not using a closure? In other words, define a function inside CustomViewController with a signature matching the getServerData callback, and pass the pointer to this function to getServerData?


Solution

  • I'm always going with Option 1 you just need to remember of using [weak self] or whatever you need to 'weakify' in order to avoid memory problems.

    Real world example:

    filterRepository.getFiltersForType(filterType) { [weak self] (categories)  in
        guard let strongSelf = self, categories = categories else { return }
        strongSelf.dataSource           = categories
        strongSelf.filteredDataSource   = strongSelf.dataSource
        strongSelf.tableView?.reloadData()
    }
    

    So in this example you can see that I pass reference to self to the completion closure, but as weak reference. Then I'm checking if the object still exists - if it wasn't released already, using guard statement and unwrapping weak value.

    Definition of network call with completion closure:

    class func getFiltersForType(type: FilterType, callback: ([FilterCategory]?) -> ()) {
        connection.getFiltersCategories(type.id).response { (json, error) in
            if let data = json {
                callback(data.arrayValue.map { FilterCategory(attributes: $0) } )
            } else {
                callback(nil)
            }
        }
    }