0

I am using a Master Detail Application. Master Screen is a Dashboard and on selecting an item, moves to the detailed screen where I trigger an Alamofire request in the backend

Below is the snippet

class APIManager: NSObject {

    class var sharedManager: APIManager {
        return _sharedManager
    }

    private var requests = [Request]()

    // Cancel any ongoing download
    func cancelRequests() {
        if requests.count > 0 {
            for request in requests {
                request.cancel()
            }
        }
    }

    func getData(completion: (dataSet: [Data]?, error: NSError?) -> Void) {
        let request = Alamofire.request(.GET, "http://request")
            .response { (request, response, data, error) in
                dispatch_async(dispatch_get_main_queue(), {
                    if(error == nil) {
                        if let response = data, data = (try? NSJSONSerialization.JSONObjectWithData(response, options: [])) as? [NSDictionary] {

                            var dataSet = [Data]()
                            for (_, dictionary) in data.enumerate() {
                                let lat = dictionary["Latitude"]
                                let lng = dictionary["Longitude"]
                                let id = dictionary["ID"] as! Int
                                let data = Data(lat: lat!, long: lng!, id: shuttleID)
                                dataSet.append(data)
                            }
                            completion(dataSet: dataSet, error: nil)
                        }
                    } else { completion(dataSet: nil, error: error) }
                })
        }
        requests.append(request)
    }
}

I have a singleton API manager class and from the detail view controller I call getData() function. Everything works fine.

But, when I push and pop repeatedly, I see rapid increase in the memory and after 10-15 attempts, I get memory warning. However in the AppDelegate I am managing it to show an Alert message and adding a delay timer for 8 seconds. But however after 20-25 attempts app crashes due to memory warning.

In viewWillDisappear(), I cancel any ongoing requests also. But I couldn't able to stop memory warning issue. I commented the part where I call the request, I see no issues, even memory consumption is less.

I welcome ideas.

Tim007
  • 2,557
  • 1
  • 11
  • 20
iOS
  • 3,526
  • 3
  • 37
  • 82

2 Answers2

0

The problem is you are never removing the requests that you append to the member variable 'requests'.

You will need to ensure to remove the request when you either cancel it or when the request completes successfully.

Do the following modifications-

    func cancelRequests() {
        if requests.count > 0 {
            for request in requests {
                request.cancel()
            }
        }
         requests.removeAll() //Delete all canseled requests
    }

also 

func getData(completion: (dataSet: [Data]?, error: NSError?) -> Void) {
        let request = Alamofire.request(.GET, "http://request")
            .response { (request, response, data, error) in
                dispatch_async(dispatch_get_main_queue(), {
                    if(error == nil) {
                        if let response = data, data = (try? NSJSONSerialization.JSONObjectWithData(response, options: [])) as? [NSDictionary] {

                            var dataSet = [Data]()
                            for (_, dictionary) in data.enumerate() {
                                let lat = dictionary["Latitude"]
                                let lng = dictionary["Longitude"]
                                let id = dictionary["ID"] as! Int
                                let data = Data(lat: lat!, long: lng!, id: shuttleID)
                                dataSet.append(data)
                            }
                            requests.removeObject(request)
                            completion(dataSet: dataSet, error: nil)
                        }
                    } else { 
                         requests.removeObject(request)
                         completion(dataSet: nil, error: error) }
                })
        }
        requests.append(request)
    }

Add this Handy extension on Array to remove item to your code:

// Swift 2 Array Extension
extension Array where Element: Equatable {
    mutating func removeObject(object: Element) {
        if let index = self.indexOf(object) {
            self.removeAtIndex(index)
        }
    }

    mutating func removeObjectsInArray(array: [Element]) {
        for object in array {
            self.removeObject(object)
        }
    }
}
Shripada
  • 6,296
  • 1
  • 30
  • 30
0

On analysis, I found that the memory warning was not due to the Alamofire request. It was due to MKMapView. Loading a MKMapView, zooming in and zooming out consumes more memory. So, in viewWillDisappear I did the fix.

override func viewWillDisappear(animated:Bool){
    super.viewWillDisappear(animated)
    self.applyMapViewMemoryFix()
}

func applyMapViewMemoryFix(){
    switch (self.mapView.mapType) {
        case MKMapType.Hybrid:
            self.mapView.mapType = MKMapType.Standard
            break;
        case MKMapType.Standard:
            self.mapView.mapType = MKMapType.Hybrid
            break;
        default:
            break;
    }
    self.mapView.showsUserLocation = false
    self.mapView.delegate = nil
    self.mapView.removeFromSuperview()
    self.mapView = nil
}

Courtesy - Stop iOS 7 MKMapView from leaking memory

Community
  • 1
  • 1
iOS
  • 3,526
  • 3
  • 37
  • 82