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
Related
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.
I am using swift 3.0 and have created a function that returns an Array of Integers. The arrays of Integers are very specific and they are gotten from a database therefore the HTTP call is asynchronous . This is a function because I use it in 3 different controllers so it makes sense to write it once . My problem is that the Async code is returned after the return statement at the bottom therefore it is returning nil . I have tried the example here Waiting until the task finishes however it is not working mainly because I need to return the value . This is my code
func ColorSwitch(label: [UILabel]) -> [Int] {
for (index, _) in label.enumerated() {
label[index].isHidden = true
}
// I need the value of this variable in the return
// statement after the async is done
var placeArea_id = [Int]()
let urll:URL = URL(string:ConnectionString+"url")!
let sessionn = URLSession.shared
var requestt = URLRequest(url: urll)
requestt.httpMethod = "POST"
let group = DispatchGroup()
group.enter()
let parameterr = "http parameters"
requestt.httpBody = parameterr.data(using: String.Encoding.utf8)
let task = sessionn.dataTask(with:requestt, completionHandler: {(data, response, error) in
if error != nil {
print("check check error")
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any]
DispatchQueue.main.async {
if let Profiles = parsedData?["Results"] as? [AnyObject] {
if placeArea_id.count >= 0 {
placeArea_id = [Int]()
}
for Profiles in Profiles {
if let pictureS = Profiles["id"] as? Int {
placeArea_id.append(pictureS)
}
}
}
group.leave()
}
} catch let error as NSError {
print(error)
}
}
})
task.resume()
group.notify(queue: .main) {
// This is getting the value however can't return it here since it
// expects type Void
print(placeArea_id)
}
// this is nil
return placeArea_id
}
I already checked and the values are returning inside the async code now just need to return it any suggestions would be great .
You will want to use closures for this, or change your function to be synchronous.
func ColorSwitch(label: [UILabel], completion:#escaping ([Int])->Void) {
completion([1,2,3,4]) // when you want to return
}
ColorSwitch(label: [UILabel()]) { (output) in
// output is the array of ints
print("output: \(output)")
}
Here's a pretty good blog about closures http://goshdarnclosuresyntax.com/
You can't really have your function return a value from an asynchronous operation within that function. That would defeat the purpose of asynchronicity. In order to pass that data back outside of your ColorSwitch(label:) function, you'll need to also have it accept a closure that will be called on completion, which accepts an [Int] as a parameter. Your method declaration will need to look something like this:
func ColorSwitch(label: [UILabel], completion: #escaping ([Int]) -> Void) -> Void {
for (index, _) in label.enumerated() {
label[index].isHidden = true
}
var placeArea_id = [Int]()
let urll:URL = URL(string:ConnectionString+"url")!
let sessionn = URLSession.shared
var requestt = URLRequest(url: urll)
requestt.httpMethod = "POST"
let group = DispatchGroup()
group.enter()
let parameterr = "http parameters"
requestt.httpBody = parameterr.data(using: String.Encoding.utf8)
let task = sessionn.dataTask(with:requestt, completionHandler: {(data, response, error) in
if error != nil {
print("check check error")
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any]
DispatchQueue.main.async {
if let Profiles = parsedData?["Results"] as? [AnyObject] {
if placeArea_id.count >= 0 {
placeArea_id = [Int]()
}
for Profiles in Profiles {
if let pictureS = Profiles["id"] as? Int {
placeArea_id.append(pictureS)
}
}
}
group.leave()
completion(placeArea_id) // This is effectively your "return"
}
} catch let error as NSError {
print(error)
}
}
})
task.resume()
}
Later on, you can call it like this:
ColorSwitch(label: []) { (ids: [Int]) in
print(ids)
}
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()
}
}
}
}
The following functions makes an API call which downloads JSON data and
passes it into an another function imp, which in turn creates the arrays!
func previewer(var spotify_id : [String])
{
var serial_number = 0
for var x in spotify_id
{
let spotify_url = "https://api.spotify.com/v1/tracks/"
let url_with_pars = spotify_url + x
let myurl_1 = NSURL(string: url_with_pars)
let timeout = 15
let request_1 = NSMutableURLRequest(URL: myurl_1!, cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 15.0)
let queue = NSOperationQueue()
request_1.HTTPMethod = "GET"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request_1, completionHandler: { (data, response, error) in
//NSURLConnection.sendAsynchronousRequest(request_1, queue: queue, completionHandler: { (reponse, data, error) in
if error != nil
{
print("Error!")
}
else
{
do
{
if let data_1 = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary
{
self.imp(data_1)
}
else
{
print("error!")
}
}
catch
{
print("error")
}
}
})
//task.resume()
}
print(artist.count)
do_table_refresh()
}
func do_table_refresh()
{
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
return
})
}
//FUNCTION WHICH CREATES ARRAY
func imp(var data_1:NSDictionary)
{
if let artist_name = data_1["artists"]![0]["name"] as? String
{
artist.append(artist_name)
}
else
{
print("artist error")
}
if let song_name = data_1["name"] as? String
{
print(song_name)
songs.append(song_name)
}
else
{
print("song error")
}
if let url = data_1["preview_url"] as? String
{
if let url_1 = data_1["id"] as? String
{
url_list.append([url, url_1])
}
else
{
print("url error")
}
}
else
{
var url_2 = data_1["id"] as? String
url_list.append(["null", url_2!])
}
}
Where exactly would you deal with the asynchronous problem any suggestion?
You should note that all the API calls are asynchronous. You are doing a loop of these so, according to your code, you could have several API calls all happening simultaneously.
// Put this outside the loop
let spotify_url = "https://api.spotify.com/v1/tracks/"
for x in spotify_id {
let url_with_pars = spotify_url + x
// Be careful. The following could be nil
let myurl_1 = NSURL(string: url_with_pars)
// Watch out for ! in the following statement.
let request_1 = NSMutableURLRequest(URL: myurl_1!, cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 15.0)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request_1, completionHandler: { (data, response, error) in
// handle data here
// refresh table here.
})
task.resume()
}
// When your code gets here, the data may not have come back yet.
// The following WILL NOT WORK
print(artist.count) // Remove me
do_table_refresh() // Remove me
Here's the problem with this approach. The table will be refreshed each time one of the API calls comes back. If you had 10 API calls, that would be 10 refreshes.
Is there a reason you use NSDictionary? Parsing JSON returns [AnyObject] which you can cast as needed.
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.