4

I wanted to load data in background thread and update tableview/UI on main thread. Based on what's indicated here about threading, I was wondering if the code below is the way to go about it. I'm trying to load more data as user scrolls to a specific index and wanted to make sure the UI is not freezing due to threading. Thank you!

 func loadMore () {
    guard !self.reachedEndOfItems else {
        return
    }

    self.offset = self.offset! + 10
    print("load more offset: \(self.offset)")

    var start = 0
    var end = 0


    isPullToRefresh = false

    let userCreds = UserDefaults.standard

    var getReqString = ""


    if userCreds.bool(forKey: "client_journal") == true || userCreds.bool(forKey: "user_journal") == true {

        var pageNum = ""
        if let pgNum = currentPgNum {
            print(pgNum)
            pageNum = String(pgNum)
        }
        var filterEntryType = ""
        if let entryTypeStr = filtEntryType {
            filterEntryType = entryTypeStr
        }

        var filterUserId = ""
        if let userId = filtUserId {
            filterUserId = userId

        }

        getReqString = "https://gethealthie.com/selected_entries.json?page=\(pageNum)&user_id=\(filterUserId)&entry_type=\(filterEntryType)&entry_filter="


    } else {

        if let pgNum = currentPgNum {
            print(pgNum)

            getReqString = "https://gethealthie.com/entries.json?page=\(pgNum)"
        }

    }


        BXProgressHUD.showHUDAddedTo(self.view)
        let request = Alamofire.request(getReqString, method: .get, headers: [
            "access-token": userCreds.object(forKey: "access-token")! as! String,
            "client": userCreds.object(forKey: "client")! as! String,
            "token-type": userCreds.object(forKey: "token-type")! as! String,
            "uid": userCreds.object(forKey: "uid")! as! String,
            "expiry": userCreds.object(forKey: "expiry")! as! String
            ]).responseJSON { (response:DataResponse<Any>) in
                print(response.response)

                let json = JSON(data: response.data!)
                print(json)

                print("yes")
                print(json.count)


                if userCreds.bool(forKey: "client_journal") == true || userCreds.bool(forKey: "user_journal") == true {
                    self.totalEntries = json["entries"].count

                    let totalEntryCount = json["entries"].count
                    start = 0
                    end = totalEntryCount
                } else {
                    self.totalEntries = json["entries"].count

                    let totalEntryCount = json["entries"].count
                    start = 0
                    end = totalEntryCount
                }

                if  self.totalEntries == 0 {
                    BXProgressHUD.hideHUDForView(self.view);

                } else if end <= self.totalEntries {
                    var jourIdx = 0

                    let newPatient = Patient()
                    let newDietitian = Dietitian()


                    for i in start ..< end {


                        let allEntries = json["entries"]
                        print(allEntries)
                        print("Entry count in loadMore is \(allEntries.count)")

                        let entry = allEntries[i]
                        print(entry)

                        let category = entry["category"]
                        print(category)


                        let name = entry["entry_comments"]
                        let k = name["id"]


                        var indexStr = String(i)

                        //entry attributes
                        self.jsonIdx.add(indexStr)

                        self.type.add(entry["type"].stringValue)
                        self.desc.add(entry["description"].stringValue)
                        self.category.add(entry["category"].stringValue)
                        //food cell- metric stat == healthy int
                        self.metric_stat.add(entry["metric_stat"].stringValue)
                        self.dateCreate.add(entry["created_at"].stringValue)
                        self.viewed.add(entry["viewed"].stringValue)
                        self.seenStatusArr.add(entry["viewed"].stringValue)
                        self.comments.add(entry["entry_comments"].rawValue)
                        self.entryType.add(entry["category"].stringValue)
                        //   "category" : entryType as AnyObject]

                        let posterInfo = entry["poster"]
                        let first = posterInfo["first_name"].stringValue
                        let last = posterInfo["last_name"].stringValue
                        let full = first + " " + last
                        self.captionName.add(full)


                        //food cell subcat
                        self.hungerInt.add(entry["percieved_hungriness"].stringValue)
                        self.prehunger.add(entry["ed_prehunger_string"].stringValue)
                        self.posthunger.add(entry["ed_posthunger_string"].stringValue)
                        self.emotions.add(entry["emotions_string"].stringValue)
                        self.reflection.add(entry["reflection"].stringValue)


                        print(self.comments)
                        self.id.add(entry["id"].stringValue)
                        self.entryImages.add(entry["image_url"].stringValue)


                        if i == end - 1 {
                            userCreds.set(json.count, forKey: "oldJsonCount")

                               BXProgressHUD.hideHUDForView(self.view)

                            DispatchQueue.main.async {
                                self.tableView.reloadData()

                            }
                    }


                } else {
                    var reachedEndOfItems = true
                    BXProgressHUD.hideHUDForView(self.view);
                    print("reached the end")
                }

    }
Community
  • 1
  • 1
mir
  • 183
  • 1
  • 12
  • how about running this code and see if it really does or does not freezes the UI? it will give you the sure answer :) – Tung Fam Jan 08 '17 at 21:45
  • seems like you only do 'reloadData' in the main queue. you also need to put everything you want into background thread since if you don't put it, it will still be in the main thread. though for example networking is by default run in background thread, but in your case it's not. – Tung Fam Jan 08 '17 at 21:51
  • @TungFam I have tried running it and it seems to be freezing the UI when I load more data at a specific index. That's why I was trying to pinpoint if the issue is threading. I was told previously I'm updating UI on a background thread. – mir Jan 08 '17 at 22:06
  • @Rob could you clarify what you mean by redundant? Should I remove `DispatchQueue.main.async` when I'm reloading the tableview? – mir Jan 08 '17 at 22:08
  • @Rob Thanks for the detailed response. Would I need to add Alamofire request to a background thread then? Previously it was wrapped in `DispatchQueue.global(qos: .background).async` to load data in background thread and `DispatchQueue.main.async` to reload tableview on main thread. However, I was told that Alamofire is async and I don't need global thread. Also, it was said that I'm updated my UI from background thread here -> http://stackoverflow.com/questions/41524332/load-data-in-tableview-upon-scrolling-down-to-a-specific-index-without-freezing – mir Jan 08 '17 at 22:23
  • @Rob That makes more sense- I'm removing both completion blocks then. Appreciate it. – mir Jan 08 '17 at 22:32

1 Answers1

3

In your code sample here, you are dispatching the reloadData to the main queue. But that is unnecessary because the closure of responseJSON is already running on the main queue, so there's no need to dispatch anything. So, you should remove that dispatch of reloadData to the main queue.

Now, if you were using URLSession, which defaults to running closures on a background queue or if you explicitly provided a background queue as the queue parameter of responseJSON, then, yes, you'd dispatch reloadData to the main queue. But that's not the only thing you'd need to make sure to dispatch to the main queue, as your model updates and the HUD updates should run on the main queue, too. But that's moot, because this responseJSON is already running its completion handler on the main queue.

Then, in comments, you later ask if all of this is running on the main queue, whether you should dispatch this all to a background queue like you did in a previous question (presumably with the intent of wanting to avoid blocking the main queue).

It turns out that this is not necessary (nor desirable) because while the processing of the response in the responseJSON completion handler runs on the main queue, the network request, itself, is performed asynchronously. You would only dispatch the completion handler code to a background queue (or specify a background queue as a parameter to responseJSON) if you were doing something computationally intensive in the closure. But you don't have to worry about the network request blocking the main queue.

Bottom line, Alamofire makes this simple for you, it runs the request asynchronously but runs its completion handlers on the main queue. It eliminates much of the manual GCD code that you mess around with when using URLSession.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044