2

I've ran into a bit of a performance issue with my iOS app, this is my first time working with NSURLSession and NSURLRequest, and although I've tried to inform myself as much as I can, I've hit a wall trying to debug a performance issue I'm facing.

So here's what I got: I've got an iOS 9 app written in Swift 2, I'm communicating with a NodeJS/Express server through Get, Post and Put Http requests, utilizing NSURLRequest and NSURLMutableRequest. I'm sending requests to fetch a group of objects (All together no more than 12000 bytes), however the requests are taking a significant amount of time (sometimes up to a minute). I've added logging to the nodeJs server and I can see that the requests take no longer than 30 milliseconds to be processed.

Note: I'm unsure if this is relevant, but I'm using a singleton "helper "class to make all my api requests and parse the results (saving authentication tokens, parsing JSON objects and saving them to Core Data, saving user preferences to NSUserDefaults, etc), I'm using a singleton so i can access it statically and I'm parsing all the data without saving anything in singleton's properties other than the URL of the server and the NSURLSession.

Here's what my code looks like.

//On initialization of the helper class
private let session = NSURLSession.sharedSession() 

func getAllObjects() {
    let route = "api/someRoute"
    let request = getRequest(route)
    request.timeoutInterval = httpTimeout
    session.dataTaskWithRequest(request, completionHandler: ResultingObjects).resume()
}

The getRequest method returns a formatted NSMutableURLRequest, shown here:

func getRequest(route: String) -> NSMutableURLRequest {
    let request = NSMutableURLRequest()
    request.URL = NSURL(string: "\(serverUrl)/\(route)")!
    request.HTTPMethod = "GET"
    request.addValue("Bearer \(self.AuthenticationToken()!)", forHTTPHeaderField: "Authorization")

    return request
}

The completion handler will parse the objects returned and notify the main thread with the resulting parsed objects, as so:

private func ResultingObjects(data: NSData?, response: NSURLResponse?, error: NSError?) {
    if let d = data {

        if !isAuthorized(d){
            return
        }
        do {
            if let JSON = try NSJSONSerialization.JSONObjectWithData(d, options: []) as? NSDictionary {

                if let message = JSON["message"] as? String {
                    if message == "Empty result" {
                        //- Return notification to be handled in main thread
                        notifyMainThread(NoObjectsFetched, payload: nil)
                        return
                    }
                }

                if let objcts = JSON["SomeObjects"] as? NSArray {
                    if let SomeObjects = parseResultingObjects(objcts) {
                        //- Return notification to be handled in main thread
                        notifyMainThread(ObjectsFetched, payload: ["payload": SomeObjects])
                    }
                    return
                }
            }
        }
        catch {
            print("Error getting resulting objects")
        }
    }
    else if let e = error {
        print("\(e), could not process GET request")
    }
}

I've also tried parsing the resulting objects on the main thread but that doesn't seem to make a difference.

If you are curious, this is how I'm sending data to the main thread:

private func notifyMainThread(notification: String, payload: AnyObject?) {
    dispatch_async(dispatch_get_main_queue(), {
        if let p = payload {
            NSNotificationCenter.defaultCenter().postNotificationName(notification, object: nil,
                userInfo: p as! [String: [MYMODEL]])
        }
        else {
            NSNotificationCenter.defaultCenter().postNotificationName(notification, object: nil)
        }
    });
}

What I've found out: Nothing makes sense! I've attempted debugging this but I cant really pin point what the issue is, When the debugger hits my "getAllObjects" method, it can take a good few seconds (up to 45 seconds) before the server logs that it received and processed the request (which it usually takes around 30 milliseconds). As far as I can tell, this happens for all requests types. Also, once the application gets the data back (super fast), it takes a long time (around 4 seconds) to parse it, and its only around 11kbs.

I've also attempted to change the requests cache policy in case the application was checking the validity of the cached records with the server, I used ReloadIgnoringLocalAndRemoteCachedData which didn't work either.

Now, this sounds like a memory leak If I pause the application at any point (after using it for a few minutes), I can see a concerning number of threads. I'm honestly not too familiar with IOS so I'm unsure if this threads are from the simulator or if they all belong to the app, the app streams video contents (which has no latency issues) usin the AVPlayer class and I believe that many of this threads are related to this, however I'm unsure if this is normal, here's a screenshot of what I mean (Note the scroll bar T_T) Screenshot

Could it be that I've got a memory leak or some zombie threads considerably slowing the performance of my app? The only really noticeable delay happens only on HTTP requests which is quite odd, no other part of my UI lags, and no other feature in my app suffers from performance issues (even streaming video contents from a url).

What would be the best way to profile this performance issues to pin point the source of the problem?

UPDATE 1: Thanks to the suggestions by Scriptable, I've managed to address the threading issue (caused by multiple AVPlayers doing their thing). The performance of the requests however are not solved.

Its worth pointing out, the server is physically in the same country as where I'm making the requests from, when making requests from the browser or form the Command Line the requests are almost immediate.

Also, when I randomly pause the app (while I wait to the request to happen) I can see a 'mach_msg_trap' in some of the threads, I'm not familiar with this but I believe this might be a race condition? or a deadlock?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Daniel Ormeño
  • 2,743
  • 2
  • 25
  • 30
  • most of the threads you have running there are from AVFoundation, looks like you have quite a few media files playing. are these streams? – Scriptable May 27 '16 at 12:25
  • Yes, They are video files played from a URL using the AVQueuePlayer from AVKit, As far as I know, I'm only actively loading a video at the time, maybe I need to dispose of the players? I'm using a view that has a queuePlayer, but this view is reused (only one view has an actively loading player at all times) – Daniel Ormeño May 27 '16 at 12:37
  • 1
    Yes, I think you need to ensure that you are not keeping strong references to them or dispose of them as needed. if these are still loading in the background then they will be using up bandwidth and connections. i dont see any problem in your networking code and you have alot of threads there that are going to use up considerable resources and battery on your device. over a mobile connection you are probably just loading too much information – Scriptable May 27 '16 at 12:49
  • Thank you for the suggestion, I'll try doing so, I'll update the question it this fixes my issue. – Daniel Ormeño May 27 '16 at 12:54
  • Thanks again, your suggestions fixed the issue with the number of threads, however this didn't really have any impact on the significant delay of the requests. I was disposing of the queuePlayer but I was doing it on deinit(), I moved the code to View did disappear which is fine because of how I use these views. Would the use of a singleton as a helper class be causing this? – Daniel Ormeño May 27 '16 at 14:32
  • i dont think so no, your network requests look ok to me, can you make the requests to your server in the browser? how long do they take then, when your running the app are you running on wifi or cellular? – Scriptable May 27 '16 at 19:45
  • Yes, requests from the browser and command line are super fast, the server is physically in the same country as I am and the requests are delayed in both the simulator and in the real device, in wifi and and cellular. I'm starting to think that there might be an issue with a race condition that's blocking the threads. – Daniel Ormeño May 28 '16 at 02:53
  • I would try to isolate the issue, personally I would create some automated tests for my API requests, you can then see how long they take to complete. Other than doing that, try to make some network requests directly as your app boots up without passing into other functions, see how long they take – Scriptable May 28 '16 at 12:35
  • Hello, Thanks heaps for your help, I was just able to find the cause of the issue. I was doing some API calls where I didn't need a reply, so i was simply not registering any completion handler for these requests, I learned the hard way that in this case, the session will timeout instead of disposing the result, which was why my other requests where super delayed. I also had to make sure that i was loading some images asynchronously, as this was adding some time to the delay. Thanks again for the follow up. – Daniel Ormeño May 28 '16 at 14:59
  • your welcome, make sure you add some caching to your requests, especially for images – Scriptable May 28 '16 at 15:20

0 Answers0