0

I am making a Swift app for iOS and I have an issue on a request I make.

This request is made every 3 to 5 seconds and the body is a 5k lines JSON (~130k characters in total) that refreshes a UITableView. The issue is that every time .dataTask is used on this specific request, the app freezes then runs normally after the request has been made.

By "freezes" I mean that I've detected it when I scroll my long UITableView. Even a small scroll list freezes.

I first suspected the update of the UITableView, but I added a small 'hack' that disables the update of the UITableView when scrolling, like this:

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        print("started scrolling")
        self.currentlyScrolling = true
    }

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        print("stopped scrolling")
        self.currentlyScrolling = false
    }

It doesn't work.

My question is then: I think that dataTask is NOT run in the main thread. But I am not sure. How can I check it? Also, how can I be sure that dataTask is async ?

Here's what the dataTask code looks like:

let task = URLSession.shared.dataTask(with: req) { data, response, err in
        guard let data = data, err == nil else {
            // error
            return
        }

        if let resp = try? JSONSerialization.jsonObject(with: data) {
            // success
        }
    }

    task.resume()

Little note however, it ONLY freezes BEFORE the JSONSerialization, not after, so I guess it is not what it is causing the freeze.

Bhavesh Nayi
  • 3,626
  • 1
  • 27
  • 42
Anthony
  • 804
  • 3
  • 12
  • 32

1 Answers1

2

URLSession.shared.dataTask runs in background thread, you can move the JSONSerialization and if you are updating the UI move that all in main thread

let task = URLSession.shared.dataTask(with: req) { data, response, err in {

    // HERE YOU ARE IN BACKGROUND THREAD, see print result in debug area

    print("You are on \(Thread.isMainThread ? "MAIN" : "BACKGROUND") thread.")

    guard let data = data, err == nil else { return }

    if let resp = try? JSONSerialization.jsonObject(with: data) {
        DispatchQueue.main.async {
            print("Now moved to \(Thread.isMainThread ? "MAIN" : "BACKGROUND") thread")

            // success, ANY UI UPDATES MUST BE MADE HERE
        }
    }
}

Your JSON parsing should be on the background thread, so this parsing operation will not block other things, such as the UI, on the main thread.

Following is an example that uses a completion block that is called on main thread

func myApiTask(with request: URLRequest, completion:@escaping (_ data: Any?) -> Void) {
    let task = URLSession.shared.dataTask(with: request) { data, response, error in

        var resp: Any?
        defer {
            DispatchQueue.main.async{ completion(resp) }
        }

        if data == nil && error == nil {
            resp = try? JSONSerialization.jsonObject(with: data!)
        } else {
            // error handling
        }

    }
}

Usage

myApiTask(with: req) { data in
    // you are on main thread
}
AamirR
  • 11,672
  • 4
  • 59
  • 73
  • Thanks for the answer. Looks like it does not solve the problem. However, I found that using `Timer` to repeat the call is smart enough to auto stop itself while the user scroll. While this does not solve the question here, it's enough for now. Thanks anyways! – Anthony Apr 27 '19 at 19:52
  • I vaguely recall that the shared session uses the main queue for its completion callback, for compatibility with NSURLConnection, so your code is probably already running on the main queue at that point. Try either creating your own session with a different (or nil) NSOperationQueue, or dispatching the entire contents of the method above explicitly onto a low-priority dispatch queue. – dgatwood May 08 '19 at 19:40