1

So I have an app that pulls up movie results when I type in a search. (Like IMDb.) I use a free API from themoviedb.org to load the results. I load them in a TableViewController. I load the posters for the results using a mod on the .dataTaskWithRequest method. to make it synchronous. Other than that, it's just basic API sending and receiving for the titles, genres, and years of the movies or TV Shows.

Now my app lags when I type too fast, this isn't completely because of the synchronous loading, because it still happens when I don't load images at all, but image loading makes the app lag, too. Now this is an issue in and of itself, but the problem is that when the app loads the words on to the screen, and is done with the lag, the results are the results of part of the word I have on screen. For example, if I type "The Simpsons" too fast, I get results for "The Sim", but if I backspace once, and retype "The Simpsons", the results reload correctly. Something that complicates things even more, is that sometimes I get the top result only being one of the old, partial results, and the rest are normal and loaded underneath.

Here is a video explaining the situation. The first time i type down "the simpsons", you can see the lag. I typed it all really fast, but it lags past the word "the". When it is done loading, it loads up a beowulf result that shouldn't even be there. I have no idea what's going on and it's driving me nuts. Even when I don't load images, and the typing doesn't lag, the results still don't update.

Here are the relevant code snippets, if you want any more, feel free to ask. I just don't want to bombard you with too much code at once:

This updates search results when text is typed in search bar:

extension SearchTable : UISearchResultsUpdating {
    func updateSearchResultsForSearchController(searchController: UISearchController) {

        //To Handle nils
        var searchBarText = searchController.searchBar.text
        if (searchBarText == nil) {
            searchBarText = ""
        }

        searchBarText! = searchBarText!.condenseWhitespace()

        //To Handle Disallowed Characters
        searchBarText = searchBarText!.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())

        //Find Results from themoviedb
        let urlString = "https://api.themoviedb.org/3/search/multi?query=" + searchBarText! + "&api_key= (I can't post the api key publicly online, sorry)"


        let results = NSURL(string: urlString)
        if (results == nil) {
            //Server Error
        }


        //Wire Up Results with matchingItems Array
        let task = NSURLSession.sharedSession().dataTaskWithURL(results!) { (data, response, error) -> Void in
            if let jsonData = data {

                do {
                    let jsonData = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers)


                    if var results = jsonData["results"] as? [NSDictionary] {
                        if results.count > 0 {
                           //Clean out non-english results:
                           //I wrote the function, it shouldn't be the source of the lag, but I can still provide it.
                            self.cleanArray(&results) 
                            self.matchingItems = results
                        } else {
                            self.matchingItems = []
                        }
                    }
                } catch {
                    //JSON Serialization Error
                }

            }
        }
        task.resume()
        self.tableView.reloadData()

    }
}

Then, after I get the results, I reload the table using the two required methods from a TableViewDataSource:

//Table Data Source
extension SearchTable {

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return matchingItems.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("cell")! as! CustomCell

        //Safe-Guard. This shouldn't be needed if I understood what I was doing
        if (indexPath.row < matchingItems.count) {
            cell.entry = matchingItems[indexPath.row] //404

            //Name & Type & Year
//This is only for TV Shows, I removed the rest for simplicity
                cell.title.text = matchingItems[indexPath.row]["name"] as? String
                cell.icon.image = UIImage(named: "tv.png")

                let date = (matchingItems[indexPath.row]["first_air_date"] as? String)
                cell.year.text = date == nil ? "" : "(" + date!.substringToIndex(date!.startIndex.advancedBy(4)) + ")"

            //Genre
            //Code here removed for simplicity

            //Poster
            cell.poster.image = UIImage(named: "Placeholder.jpg")
            if let imagePath = matchingItems[indexPath.row]["poster_path"] as? String {
                let url = NSURL(string: "http://image.tmdb.org/t/p/w185" + imagePath)
                let urlRequest = NSURLRequest(URL: url!)
                let session = NSURLSession.sharedSession()
                //Synchronous Request
                let semaphore = dispatch_semaphore_create(0)
                let task = session.dataTaskWithRequest(urlRequest) { data, response, error in
                    if let poster = UIImage(data: data!) {
                        cell.poster.image = poster
                    }
                    dispatch_semaphore_signal(semaphore)
                }
                task.resume()
                dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
            }
        }

        return cell
    }

}

Thanks!

JoeVictor
  • 1,806
  • 1
  • 17
  • 38

2 Answers2

0

First of all, I strongly recommend you don't use synchronous request, mainly because it blocks your UI until it finish and this is a very bad responsiveness for an app.

In your case you can place a placeholder for the UIImage and when the request finish substitute it for the correct image.

Regarding your issue of typing faster, it's called throttle or debounce, Apple recommends:

Performance issues. If search operations can be carried out very rapidly, it is possible to update the search results as the user is typing by implementing the searchBar:textDidChange: method on the delegate object. However, if a search operation takes more time, you should wait until the user taps the Search button before beginning the search in the searchBarSearchButtonClicked: method. Always perform search operations a background thread to avoid blocking the main thread. This keeps your app responsive to the user while the search is running and provides a better user experience.

But if you until want it to handle yourself you can see this two good answers explaining how to handle it correctly:

I recommend you handle it as Apple recommends or you can change your philosophy and adopt some libraries that handle it for your automatically like:

The first one in more easy to learn, the second one needs to learn Reactive Programming and concepts of Functional Programming, It's up to you.

I hope this help you.

Community
  • 1
  • 1
Victor Sigler
  • 23,243
  • 14
  • 88
  • 105
  • That is perfect! Throttling the search is exactly what I wanted! Thank You so much!! Also, I realized that the reason why the search results were still buggy even when I didn't load images. Everytime I searched, it was done asynchronously, so sometimes the table would reload before searches were done. [This helped me a lot](http://stackoverflow.com/questions/35421631/in-swift-tableview-data-loaded-from-json-only-loads-80-of-the-time) Using both of these, I'm pretty confident in solving this. Thank you thank you! This has been troubling me for three days! – JoeVictor May 21 '16 at 00:15
  • Just a question, though, what do Bond and RxSwift do exactly? I opened their documentation and couldn't really understand what they can do to help me... – JoeVictor May 21 '16 at 00:30
  • Well Bond is just a library to make data binding and for example you could adopt the MVVM patterns instead of the default classic MVC proposed by Apple, with Bond you can handle the throttle. In the other way RxSwift is a library to adopt another concept called Reactive Programming, if you want a really well explained introduction to RxSwift I recommend you this article http://www.thedroidsonroids.com/blog/ios/rxswift-by-examples-1-the-basics/ – Victor Sigler May 21 '16 at 13:16
  • I know comments like these aren't really encouraged, but seriously, thanks. You've been of so much help! – JoeVictor May 21 '16 at 18:21
0

Just for people who may be struggling in the future with this same issue. First of all, read my comment to Victor Sigler's answer.

Here were the issues:

1 - I searched for the results online using .dataTaskWithURL() This is an asynchronous method which ran in the background while the code continued. So on occasion, the table would reload before the new results were in. See this thread for more information. I highly recommend checking this tutorial on concurrency if you are serious about learning Swift:

www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1

2 - The images lagged because of the search being synchronous, as Victor said. His answer pretty much handles the rest, so read it!

Community
  • 1
  • 1
JoeVictor
  • 1,806
  • 1
  • 17
  • 38