performBackgroundTask not saving when app in background Swift - ios

I'm using Xcode 9 and Swift 4. I start a download of multiple JSON files when the application is in the foreground. The application then parses these files and saves them to CoreData. This works well when the application is in the foreground. However, if the application is in the background, the files still download correctly, but the data is not parsed and saved to CoreData. It's only when the user returns to the foreground that the parsing and saving of data continues.
I have Background Modes turned on - Background Fetch and Remote notifications.
I have around 10 functions that are similar to the one below in which it processes the JSON files concurrently:
func populateStuff(json: JSONDictionary) -> Void {
let results = json["result"] as! JSONDictionary
let stuffData = results["Stuff"] as! [JSONDictionary]
let persistentContainer = getPersistentContainer()
persistentContainer.performBackgroundTask { (context) in
for stuff in stuffData {
let newStuff = Stuff(context: context)
newStuff.stuffCode = stuff["Code"] as? String
newStuff.stuffDescription = stuff["Description"] as? String
do {
try context.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
}
}
func getPersistentContainer() -> NSPersistentContainer {
let persistentContainer = NSPersistentContainer(name: "MyProjectName")
persistentContainer.loadPersistentStores { (_, error) in
if let error = error {
fatalError("Failed to load core data stack: \(error.localizedDescription)")
}
}
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
persistentContainer.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
return persistentContainer
}
Can anyone advise me on why this might happen and how to over come this?
TIA

Use the beginBackgroundTaskWithName:expirationHandler: method:
func populateStuff(json: JSONDictionary) -> Void {
// Perform the task on a background queue.
DispatchQueue.global().async {
// Request the task assertion and save the ID.
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask (withName: "Finish Network Tasks") {
// End the task if time expires.
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = UIBackgroundTaskInvalid
}
// Parse the json files
let results = json["result"] as! JSONDictionary
let stuffData = results["Stuff"] as! [JSONDictionary]
let persistentContainer = getPersistentContainer()
persistentContainer.performBackgroundTask { (context) in
for stuff in stuffData {
let newStuff = Stuff(context: context)
newStuff.stuffCode = stuff["Code"] as? String
newStuff.stuffDescription = stuff["Description"] as? String
do {
try context.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
}
// End the task assertion.
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = UIBackgroundTaskInvalid
}
Calling this method gives you extra time to perform important tasks. Notice the use of endBackgroundTask: method right after the task is done. It lets the system know that you are done. If you do not end your tasks in a timely manner, the system terminates your app.

Related

Swift: How to refresh tableview without lag (UIRefreshControl)

Filling my table view with objects from a MYSQL database using PHP and JSON to Swift 3. I have a pull down to refresh function but when I'm pulling down to refresh it lags mid way for a second and then continues (like the wheel won't spin for a second).
How can I update my tableview smoother because I'm guessing as I add more content to the database in the future the bigger the lag. I currently have 12 objects in my database so imagine with 100+ objects.
In viewDidLoad
// Pull to Refresh
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
if #available(iOS 10.0, *) {
myTableView.refreshControl = refreshControl
print("iOS 10")
} else {
myTableView.addSubview(refreshControl)
print("iOS 9 or iOS 8")
}
Then pull to refresh function
// Pull to Refresh
func handleRefresh(refreshControl: UIRefreshControl) {
// Fetching Data for TableView
retrieveDataFromServer()
// Stop Refreshing
refreshControl.endRefreshing()
}
// Retrieving Data from Server
func retrieveDataFromServer() {
// Loading Data from File Manager
loadData()
let getDataURL = "http://example.com/receiving.php"
let url: NSURL = NSURL(string: getDataURL)!
do {
let data: Data = try Data(contentsOf: url as URL)
let jsonArray = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! NSMutableArray
// Clear the arrays
self.followedArray = [Blog]()
self.mainArray = [Blog]()
// Looping through jsonArray
for jsonObject in jsonArray {
if let blog = Blog(jsonObject:jsonObject as! [String : Any]) {
// Check if Identifiers Match
if followedIdentifiers.contains(blog.blogID) {
self.followedArray.append(blog)
} else {
self.mainArray.append(blog)
}
}
}
} catch {
print("Error: (Retrieving Data)")
}
myTableView.reloadData()
}
Refer to the apple sample code at the following location:
http://developer.apple.com/library/ios/#samplecode/LazyTableImages/Introduction/Intro.html
Couple of suggestion :
Don’t show the data at cellForRowAtIndexPath: method ‘cause at this time cell is not displayed yet. Try to use tableView:willDisplayCell:forRowAtIndexPath: method in the delegate of UITableView.
Re-Use single instance of cell/header/footer even if you need to show more.
Let me know if anything specific is needed.
Spinner.isHidden = false
Spinner.startAnimating()
DispatchQueue.global(qos: .background).async {
loadData()
let getDataURL = "http://example.com/receiving.php"
let url: NSURL = NSURL(string: getDataURL)!
do {
let data: Data = try Data(contentsOf: url as URL)
let jsonArray = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! NSMutableArray
// Clear the arrays
self.followedArray = [Blog]()
self.mainArray = [Blog]()
// Looping through jsonArray
for jsonObject in jsonArray {
if let blog = Blog(jsonObject:jsonObject as! [String : Any]) {
// Check if Identifiers Match
if followedIdentifiers.contains(blog.blogID) {
self.followedArray.append(blog)
} else {
self.mainArray.append(blog)
}
}
}
} catch {
print("Error: (Retrieving Data)")
}
DispatchQueue.main.async
{
myTableView.reloadData()
self.Spinner.startAnimating()
self.Spinner.isHidden = true
}
}
My guess is that your retrieveDataFromServer() is blocking the main thread, and therefore causing the lag. Try wrapping it in an async block
// Pull to Refresh
func handleRefresh(refreshControl: UIRefreshControl) {
// Fetching Data for TableView
retrieveDataFromServer { [weak refreshControl] in
// This block will run once retrieveDataFromServer() is completed
// Reload data
myTableView.reloadData()
// Stop Refreshing
refreshControl?.endRefreshing()
}
}
// Retrieving Data from Server
func retrieveDataFromServer(completion: (() -> Void)?) {
// Loading Data from File Manager
loadData()
let getDataURL = "http://example.com/receiving.php"
let url: NSURL = NSURL(string: getDataURL)!
do {
let data: Data = try Data(contentsOf: url as URL)
let jsonArray = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! NSMutableArray
// Clear the arrays
self.followedArray = [Blog]()
self.mainArray = [Blog]()
// Looping through jsonArray
for jsonObject in jsonArray {
if let blog = Blog(jsonObject:jsonObject as! [String : Any]) {
// Check if Identifiers Match
if followedIdentifiers.contains(blog.blogID) {
self.followedArray.append(blog)
} else {
self.mainArray.append(blog)
}
}
}
} catch {
print("Error: (Retrieving Data)")
}
// Calls completion block when finished
completion?()
}
I imagine the lag you're experiencing is due to the network request being executed synchronously on the main thread:
let data: Data = try Data(contentsOf: url as URL)
Network requests are slow and should almost certainly be done off the main thread. The solution here is to move the networking call to a background thread so the main (UI) thread doesn't get blocked (lag).
So how do you do that? Well that is a large question with many different answers.
I highly recommend you spend some time learning about multi-threaded programming (also known as concurrency) in Swift. Going through this Ray Wenderlich tutorial should give you a good foundation.
Then it's probably a good idea to learn about URLSession which is used for performing asynchronous network requests in iOS apps. Again Ray Wenderlich has a great starter tutorial.
Finally... here is a quick and dirty solution for you. It's "hacky" and you probably shouldn't use it, but it will probably fix your lag issue:
func retrieveDataFromServer() {
// Loading Data from File Manager
loadData()
let getDataURL = "http://example.com/receiving.php"
let url: NSURL = NSURL(string: getDataURL)!
// Move to a background queue to fetch and process data from network.
DispatchQueue.global().async {
// Don't touch anything related to the UI here.
do {
let data: Data = try Data(contentsOf: url as URL)
let jsonArray = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! NSMutableArray
// Create new temp arrays to process json
var tempFollowedArray = [Blog]()
var tempMainArray = [Blog]()
// Looping through jsonArray
for jsonObject in jsonArray {
if let blog = Blog(jsonObject:jsonObject as! [String : Any]) {
// Check if Identifiers Match
if self.followedIdentifiers.contains(blog.blogID) {
tempFollowedArray.append(blog)
} else {
tempMainArray.append(blog)
}
}
}
// Jump back to main (UI) thread to update results
DispatchQueue.main.async {
print("success")
self.followedArray = tempFollowedArray
self.mainArray = tempMainArray
self.myTableView.reloadData()
}
} catch {
DispatchQueue.main.async {
print("Error: (Retrieving Data)")
// This reload is probably not necessary, but it was
// in your original code so I included it.
self.myTableView.reloadData()
}
}
}
}

Application going to background, data is not loaded

I created a simple json application and working fine but when the app is going to background data is not loaded,some one please suggest me with code.
let task = URLSession.shared.dataTask(with: URL(string: "http://tour-pedia.org/api/getReviews?location=Rome&category=poi")!) { (data, response , error) in
if error != nil {
print(error?.localizedDescription ?? "")
}
if let resultArray = (try? JSONSerialization.jsonObject(with: data!, options: [])) as? [[String:Any]] {
for jsonreviews in resultArray {
let review = Review()
review.rating = jsonreviews["rating"] as? Int ?? 0
review.text = jsonreviews["text"] as! String
review.time = jsonreviews["time"] as! String
reviews.append(review)
}
DispatchQueue.main.async {
self.tableview.reloadData() // if you use tableview
}
}
}
task.resume()
The simple solution, if you only need an extra few minutes to finish the request, is to request the OS a little time to finish this. See Background Execution: Executing Finite-Length Tasks.
So, to tell the OS that you might want a little extra time to run the request if the app happens to be suspended while the task is running, you can do:
var backgroundTask = UIBackgroundTaskInvalid
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "com.domain.app.data") {
// do whatever clean up you want before your app exits
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
And, when the task is done:
if backgroundTask != UIBackgroundTaskInvalid {
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
Thus:
var backgroundTask = UIBackgroundTaskInvalid
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "com.domain.app.data") {
// do whatever clean up you want before your app exits
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
let task = URLSession.shared.dataTask(with: URL(string: "http://tour-pedia.org/api/getReviews?location=Rome&category=poi")!) { data, response, error in
guard let data = data, error != nil else {
print(error?.localizedDescription ?? "")
return
}
if let resultArray = (try? JSONSerialization.jsonObject(with: data)) as? [[String: Any]] {
for jsonreviews in resultArray {
let review = Review()
review.rating = jsonreviews["rating"] as? Int ?? 0
review.text = (jsonreviews["text"] as! String)
review.time = (jsonreviews["time"] as! String)
reviews.append(review)
}
DispatchQueue.main.async {
self.tableview.reloadData() // if you use tableview
}
}
if backgroundTask != UIBackgroundTaskInvalid {
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
}
task.resume()
If you think you'll need more than a few minutes to finish the query, you can implement background URLSession, though that might be overkill for this scenario. It requires quite a few changes including:
Use delegate-based URLSession;
Use download task rather than data task;
Make sure to implement app delegate method to respond to completion of background network request;
etc.
Please check this Downloading in background using Swift 3.0

Saving in background causes response time to be delayed (iOS)

I have a table, which uses a NSFetchedResultsController to populate it's data. When I refresh my table, I need to update all 50+ items, so I do the following: I make a call to the server which returns JSON data, store the "media" object into an array, loop through this array and individually store each object to core data (in background thread), then reload the table. This works fine. However there is a major issue.
Sometimes the step of saving to the database takes 7+ seconds, due to looping through large arrays and individually storing each object to core data. And while this step is executing, when I fetch other data from the server, the response time is delayed tremendously. I wont be able to fetch new data until the save process is complete. I'm quite confused because this is supposed to be done in the background thread and not block other server calls.
Why does saving data to core data in bg causing my response time to be delayed? Is there a better approach to storing large arrays to core data without disrupting any responses?
//Refreshing User Table method
class func refreshUserProfileTable(callback: (error: NSError?) -> Void) {
//getProfile fetches data from server
ProfileWSFacade.getProfile(RequestManager.userID()!) {
(profile, isLastPage, error) -> () in
DataBaseManager.sharedInstance.saveInBackground({ (backgroundContext) in
let mediaList = profile?["media"] as? Array<JSONDictionary>
if let mediaList = mediaList {
//Response time is delayed when this loop is executing
for media in mediaList {
DataBaseManager.sharedInstance.storeObjectOfClass(Media.self, dict: media, context: backgroundContext)
}
}
}, completion: {
callback(error: error)
})
}
}
//MARK: Core data methods:
//Save in background method in Database manager
func saveInBackground(
block: (backgroundContext: NSManagedObjectContext) -> Void,
completion: (Void->Void)? = nil)
{
let mainThreadCompletion = {
if let completion = completion {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion()
})
}
}
backgroundContext.performBlock { () -> Void in
guard RequestManager.userID() != nil else {
mainThreadCompletion()
return
}
block(backgroundContext: self.backgroundContext)
if RequestManager.userID() != nil {
_ = try? self.backgroundContext.save()
DataBaseManager.sharedInstance.save()
}
mainThreadCompletion()
}
}
//Stores class object
func storeObjectOfClass<T: NSManagedObject where T: Mappable>(
entityClass:T.Type,
dict: JSONDictionary,
context: NSManagedObjectContext? = nil) -> T
{
let context = context ?? mainManagedObjectContext
let predicate = NSPredicate(format: "%K LIKE %#", entityClass.primaryKey(), entityClass.primaryKeyFromDict(dict))
let requestedObject = DataBaseManager.createOrUpdateFirstEntity(
entityType: T.self,
predicate: predicate,
context: context) { (entity) -> () in
entity.populateFromDictionary(dict)
}
return requestedObject
}
//Creates or updates core data entity
class func createOrUpdateFirstEntity<T: NSManagedObject>(
entityType entityType: T.Type,
predicate: NSPredicate,
context: NSManagedObjectContext,
entityUpdateBlock:(entity: T) -> ()) -> T
{
guard DataBaseManager.sharedInstance.doPersistentStoreAvailible() else { return T() }
let desc = NSEntityDescription.entityForName(String(entityType), inManagedObjectContext: context)!
let existingEntityRequest = NSFetchRequest()
existingEntityRequest.entity = desc
existingEntityRequest.predicate = predicate
let requestedObject = try? context.executeFetchRequest(existingEntityRequest).first
if let requestedObject = requestedObject as? T {
entityUpdateBlock(entity: requestedObject)
return requestedObject
} else {
let newObject = T(entity: desc, insertIntoManagedObjectContext: context)
entityUpdateBlock(entity: newObject)
return newObject
}
}
I found out that .performBlock follows the FIFO rule, first in, first out. Meaning the blocks will be executed in the order in which they were put into the internal queue: SO Link. Because of that, the next rest call would wait until the first block has completed before it saved, and did its callback. The actual response time wasnt slow, it was just the saving time because of FIFO.
The solution was to use a different NSManagedContext for profile loading, rather than using the one that was being used for all background calls.
let profileContext: NSManagedObjectContext
//Instead of calling saveInBackground, we save to saveInProfileContext, which wont block other rest calls.
func saveInProfileContext(
block: (profileContext: NSManagedObjectContext) -> Void,
completion: (Void->Void)? = nil)
{
let mainThreadCompletion = {
if let completion = completion {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion()
})
}
}
profileContext.performBlock { () -> Void in
guard RequestManager.userID() != nil else {
mainThreadCompletion()
return
}
block(profileContext: self.profileContext)
if RequestManager.userID() != nil {
_ = try? self.profileContext.save()
DataBaseManager.sharedInstance.save()
}
mainThreadCompletion()
}
}

CoreData Concurrency issue

I am having issue while using private managedObjectContextfor saving data in background. I am new to CoreData. I am using Parent-Child approach for NSManagedObjectContext but facing several issues.
Errors arise when I tap reload button multiple times
Errors:
'NSGenericException', reason: Collection <__NSCFSet: 0x16e47100> was mutated while being enumerated
Some times : crash here try managedObjectContext.save()
Sometimes Key value coding Compliant error
My ViewController class
class ViewController: UIViewController {
var jsonObj:NSDictionary?
var values = [AnyObject]()
#IBOutlet weak var tableView:UITableView!
override func viewDidLoad() {
super.viewDidLoad()
getData()
saveInBD()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.saved(_:)), name: "kContextSavedNotification", object: nil)
}
//Loding json data from a json file
func getData(){
if let path = NSBundle.mainBundle().pathForResource("countries", ofType: "json") {
do {
let data = try NSData(contentsOfURL: NSURL(fileURLWithPath: path), options: NSDataReadingOptions.DataReadingMappedIfSafe)
do {
jsonObj = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
} catch {
jsonObj = nil;
}
} catch let error as NSError {
print(error.localizedDescription)
}
} else {
print("Invalid filename/path.")
}
}
**Notification reciever**
func saved(not:NSNotification){
dispatch_async(dispatch_get_main_queue()) {
if let data = DatabaseManager.sharedInstance.getAllNews(){
self.values = data
print(data.count)
self.tableView.reloadData()
}
}
}
func saveInBD(){
if jsonObj != nil {
guard let nameArray = jsonObj?["data#"] as? NSArray else{return}
DatabaseManager.sharedInstance.addNewsInBackGround(nameArray)
}
}
//UIButton for re-saving data again
#IBAction func reloadAxn(sender: UIButton) {
saveInBD()
}
}
**Database Manager Class**
public class DatabaseManager{
static let sharedInstance = DatabaseManager()
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
private init() {
}
func addNewsInBackGround(arr:NSArray) {
let jsonArray = arr
let moc = managedObjectContext
let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = moc
privateMOC.performBlock {
for jsonObject in jsonArray {
let entity = NSEntityDescription.entityForName("Country",
inManagedObjectContext:privateMOC)
let managedObject = NSManagedObject(entity: entity!,
insertIntoManagedObjectContext: privateMOC) as! Country
managedObject.name = jsonObject.objectForKey("name")as? String
}
do {
try privateMOC.save()
self.saveMainContext()
NSNotificationCenter.defaultCenter().postNotificationName("kContextSavedNotification", object: nil)
} catch {
fatalError("Failure to save context: \(error)")
}
}
}
func getAllNews()->([AnyObject]?){
let fetchRequest = NSFetchRequest(entityName: "Country")
fetchRequest.resultType = NSFetchRequestResultType.DictionaryResultType
do {
let results =
try managedObjectContext.executeFetchRequest(fetchRequest)
results as? [NSDictionary]
if results.count > 0
{
return results
}else
{
return nil
}
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
return nil
}
}
func saveMainContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
let nserror = error as NSError
print("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
You can write in background and read in the main thread (using different MOCs like you do). And actually you're almost doing it right.
The app crashes on the try managedObjectContext.save() line, because saveMainContext is called from within the private MOC's performBlock. The easiest way to fix it is to wrap the save operation into another performBlock:
func saveMainContext () {
managedObjectContext.performBlock {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
let nserror = error as NSError
print("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Other two errors are a little more tricky. Please, provide more info. What object is not key-value compliant for what key? It's most likely a JSON parsing issue.
The first error ("mutated while being enumerated") is actually a nasty one. The description is pretty straight forward: a collection was mutated by one thread while it was enumerated on the other. Where does it occur?
One possible reason (most likely one, I would say) is that it is indeed a Core Data multithreading issue. Despite the fact that you can use several threads, you can only use core data objects within the thread they were obtained on. If you pass them to another thread, you'll likely run into an error like this.
Look through your code and try to find a place where such situation might occur (for instance, do you access self.values from other classes?). Unfortunately, I wasn't able to find such place in several minutes. If you said upon which collection enumeration this error occurs, it would help).
UPDATE:
P.S. I just thought that the error might be related to the saveMainContext function. It is performed right before a call to saved. saveMainContext is performed on the background thread (in the original code, I mean), and saved is performed on the main thread. So after fixing saveMainContext, the error might go away (I'm not 100% sure, though).
You are violating thread confinement.
You cannot write to CoreData in Background, and read in MainThread.
All operation on CoreData must be done in the same thread

iOS - Why reloadData tableView data on first application load?

I am working on a simple Flickr app that gets some data from their API and displays it on a tableview instance. Here's a piece of the code for the TableViewController subclass.
var photos = [FlickrPhotoModel]()
override func viewDidLoad() {
super.viewDidLoad()
getFlickrPhotos()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
private func getFlickrPhotos() {
DataProvider.fetchFlickrPhotos { (error: NSError?, data: [FlickrPhotoModel]?) in
//data is received
dispatch_async(dispatch_get_main_queue(), {
if error == nil {
self.photos = data!
self.tableView.reloadData()
}
})
}
}
The application does not seem to load the data if the { tableView.reloadData() } line is removed. Does anyone know why this would happen since I call getFlickrPhotos() within viewDidLoad(). I believe I am also dispatching from the background thread in the appropriate place. Please let me know what I am doing incorrectly.
EDIT -- Data Provider code
class func fetchFlickrPhotos(onCompletion: FlickrResponse) {
let url: NSURL = NSURL(string: "https://api.flickr.com/services/rest/?method=flickr.photos.getRecent&api_key=\(Keys.apikey)&per_page=25&format=json&nojsoncallback=1")!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
if error != nil {
print("Error occured trying to fetch photos")
onCompletion(error, nil)
return
}
do {
let jsonResults = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
let photosContainer = jsonResults!["photos"] as? NSDictionary
let photoArray = photosContainer!["photo"] as? [NSDictionary]
let flickrPhoto: [FlickrPhotoModel] = photoArray!.map{
photo in
let id = photo["id"] as? String ?? ""
let farm = photo["farm"] as? Int ?? 0
let secret = photo["secret"] as? String ?? ""
let server = photo["server"] as? String ?? ""
var title = photo["title"] as? String ?? "No title available"
if title == "" {
title = "No title available"
}
let model = FlickrPhotoModel(id: id, farm: farm, server: server, secret: secret, title: title)
return model
}
//the request was successful and flickrPhoto contains the data
onCompletion(nil, flickrPhoto)
} catch let conversionError as NSError {
print("Error parsing json results")
onCompletion(conversionError, nil)
}
}
task.resume()
}
I'm not familiar with that API, but it looks like the fetchFlickrPhotos method is called asynchronously on a background thread. That means that the rest of the application will not wait for it to finish before moving on. viewDidLoad will call the method, but then move on without waiting for it to finish.
The completion handler that you provide is called after the photos are done downloading which, depending on the number and size of the photos, could be seconds later. So reloadData is necessary to refresh the table view after the photos are actually done downloading.

Resources