I'm working on an app in iOS wherein I need to start spinning a UIActivityIndicatorView, upload an image to a server, and when the upload is completed, stop spinning the activity indicator.
I'm currently using XCode 7 Beta and am testing the app on the iOS simulator as an iPhone 6 and iPhone 5. My issue is that the activity indicator won't end immediately after file upload, but several (~28 seconds) later. Where should I place my calls to cause it to end?
I have an #IBOutlet function attached to the button I use to start the process, which contains the startAnimating() function, and which calls a dispatch_async method that contains the call to uploadImage, which contains the signal, wait, and stopAnimating() functions.
Note that
let semaphore = dispatch_semaphore_create(0)
let priority = DISPATCH_QUEUE_PRIORITY_HIGH
are defined at the top of my class.
#IBAction func uploadButton(sender: AnyObject) {
self.activityIndicatorView.startAnimating()
dispatch_async(dispatch_get_global_queue(priority, 0)) {
self.uploadImage(self.myImageView.image!)
} // end dispatch_async
} // works with startAnimating() and stopAnimating() in async but not with uploadImage() in async
func uploadImage(image: UIImage) {
let request = self.createRequest(image)
let session : NSURLSession = NSURLSession.sharedSession()
let task : NSURLSessionTask = session.dataTaskWithRequest(request, completionHandler: {
(data, response, error) in
if error != nil {
print(error!.description)
} else {
let httpResponse: NSHTTPURLResponse = response as! NSHTTPURLResponse
if httpResponse.statusCode != 200 {
print(httpResponse.description)
} else {
print("Success! Status code == 200.")
dispatch_semaphore_signal(self.semaphore)
}
}
})! // end dataTaskWithResult
task.resume()
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER)
self.activityIndicatorView.stopAnimating()
} // end uploadImage
This is just one version of my code, I have moved several things around several different ways. I have tried this:
#IBAction func uploadButton(sender: AnyObject) {
self.activityIndicatorView.startAnimating()
dispatch_async(dispatch_get_global_queue(priority, 0)) {
self.uploadImage(self.myImageView.image!)
dispatch_semaphore_signal(self.semaphore)
} // end dispatch_async
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER)
self.activityIndicatorView.stopAnimating()
}
And several, several other ways of moving my code around to attempt to get the activity indicator to display for the duration of the image upload and then immediately quit. In some cases the spinner doesn't appear at all for the duration of program execution. I read this post and this question and have migrated my dispatch_semaphore_wait and stopAnimating() to the uploadImage() method to circumvent this, but can't find enough information in the UIActivityIndicatorView documentation about the UI updating to know any other way of updating it, though I believe this might be at the core of the problem.
All I need is for the spinner to start before the upload process begins (dataTaskWithRequest) and end once it has succeeded or failed. What am I doing wrong?
Instead of using semaphores, you could just dispatch directly to the main thread in your async task,
func uploadImage(image: UIImage) {
let request = self.createRequest(image)
let session : NSURLSession = NSURLSession.sharedSession()
let task : NSURLSessionTask = session.dataTaskWithRequest(request, completionHandler: {
(data, response, error) in
if error != nil {
print(error!.description)
} else {
let httpResponse: NSHTTPURLResponse = response as! NSHTTPURLResponse
if httpResponse.statusCode != 200 {
print(httpResponse.description)
} else {
print("Success! Status code == 200.")
}
}
// dispatch to main thread to stop activity indicator
dispatch_async(disptach_get_main_queue()) {
self.activityIndicatorView.stopAnimating()
}
})! // end dataTaskWithResult
task.resume()
} // end uploadImage
Related
I am a new developer. I'm using Swift 4.2 and Xcode 10.2.
I'm trying to show a spinner while a photo is being saved. I am simulating a slow connection on my iPhone in Developer Mode to test it. Using the code below the spinner does not show up. The view goes right to the end where the button shows and says the upload is complete (when it isn't). I tried putting it all into a DispatchQueue.global(qos: .userinitiated).async and then showing the button back on the main queue. I also tried putting the showSpinner on a DispatchQueue.main and then savePhoto on a .global(qos: .utility). But I cleary don't understand the GCD processes.
Here's my code:
func savePhoto(image:UIImage) {
// Add a spinner (from the Extensions)
self.showSpinner(onView: self.view)
PhotoService.savePhoto(image: image) { (pct) in
// Can show the loading bar here.
}
// Stop the spinner
self.removeSpinner()
// Show the button.
self.goToPhotosButtonLabel.alpha = 1
self.doneLabel.alpha = 1
}
What types of DispatchQueues should I use and where should I put them?
Here is the savePhoto code:
static func savePhoto(image:UIImage, progressUpdate: #escaping (Double) -> Void) {
// Get data representation of the image
let photoData = image.jpegData(compressionQuality:0.1)
guard photoData != nil else {
print("Couldn't turn the image into data")
return
}
// Get a storage reference
let userid = LocalStorageService.loadCurrentUser()?.userId
let filename = UUID().uuidString
let ref = Storage.storage().reference().child("images/\(String(describing: userid))/\(filename).jpg")
// Upload the photo
let uploadTask = ref.putData(photoData!, metadata: nil) { (metadata, error) in
if error != nil {
// An error during upload occurred
print("There was an error during upload")
}
else {
// Upload was successful, now create a database entry
self.createPhotoDatabaseEntry(ref: ref, filename: filename)
}
}
uploadTask.observe(.progress) { (snapshot) in
let percentage:Double = Double(snapshot.progress!.completedUnitCount /
snapshot.progress!.totalUnitCount) * 100.00
progressUpdate(percentage)
}
}
Since the code that saves the photo is asynchronous , so your current code removes the spinner directly after it's added before the upload is complete
func savePhoto(image:UIImage) {
// Add a spinner (from the Extensions)
self.showSpinner(onView: self.view)
PhotoService.savePhoto(image: image) { (pct) in
// remove spinner when progress is 100.0 = upload complete .
if pct == 100.0 {
// Stop the spinner
self.removeSpinner()
// Show the button.
self.goToPhotosButtonLabel.alpha = 1
self.doneLabel.alpha = 1
}
}
}
Here you don't have to use GCD as firebase upload runs in another background thread so it won't block the main thread
Excuse me if this a noobish question but I don't know the difference between executing a block of code after an API request is received and parsed via GCD, delegates and closures.
As far as I know, a creating a session to download data from an API URL is done on the main thread unless I execute the code inside a a GCD block or a delegate or a closure.
Here are two examples:
Using GCD
DispatchQueue.global(qos: .utility).async {
let requestURL = URL(string: "http://echo.jsontest.com/key/value/one/two")
let session = URLSession.shared
let task = session.dataTask(with: requestURL!) {
(data, response, error) in
print(data as Any)
DispatchQueue.main.async {
print("Hello")
}
}
task.resume()
}
Using Delegate:
import Foundation
import UIKit
protocol WeatherDataDownloaderProtocol {
func setData(weatherData: WeatherData)
}
class WeatherDataDownloader {
var weatherData = WeatherData()
var delegate: WeatherDataDownloaderProtocol?
func downloadWeatherData() {
let API_URL = WEATHER_FORECAST_URL
guard let URL = URL(string: API_URL) else {
print("Error: No valid URL")
return
}
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: URL) { (data, response, error) in
guard error == nil else {
print("Error getting data")
print("\(error)")
return
}
guard let responseData = data else {
print("Error: Did not receive data")
return
}
do {
guard let JSON = try JSONSerialization.jsonObject(with: responseData, options: []) as? Dictionary<String, AnyObject> else {
print("Error: Error trying to convert data to JSON")
return
}
print(JSON)
self.sendDataBack()
} catch {
print("Error: Parsing JSON data error")
return
}
}
task.resume()
}
func sendDataBack() {
if let _delegate = delegate {
_delegate.setData(weatherData: weatherData)
}
}
}
Both, print("Hello") and print(JSON) + self.sendDataBack() will execute after the JSON is retrieved and parsed. What's the difference between both methods? Does it have anything to do with whether my app would crash if I navigate out of the viewController while waiting for the network response?
Thanks a lot
In your first approach, the .async call is not necessary. URLSession dataTask is a background task.
So the choice is not GDC vs. delegates but completion handler vs. delegate.
Opinion based:
Using a delegate is more work and harder to read because you have to check in other areas of the code if the delegate is actually set and who it is and what it actually does.
Also no code might be executed in case the delegate does not exist any more at the time your network call has finished. So for this case I plead for using a completion closure.
Both are correct. The block/closure approach is newer and considered to have better readability since you don't have to jump between functions and even between files to follow the course of your code.
DispatchQueue.global(qos: .utility).async {
let requestURL = URL(string: "http://echo.jsontest.com/key/value/one/two")
let session = URLSession.shared
let task = session.dataTask(with: requestURL!) {
(data, response, error) in
print(data as Any)
DispatchQueue.main.async {
print("Hello")
}
}
task.resume()
}
In this method your service hits in background thread and when you completed your in background thread you come back in main thread using this method
DispatchQueue.main.async {
print("Hello")
}
and then your print("Hello") will call in main thread.
While the method
downloadWeatherData
defined in appdelegate also hits the service in background thread but in the manner of closure because closure also works like a background thread. Using closure when your task completes your control automatically comes back in main thread where you call print(JSON).
Now comes to your problem, the best thing is that you should wait untill your task complete and you get the json response on your viewcontroller then move to your next controller other your app may crash in some situations.
I am trying to use grand central dispatch to wait for files to finish download before continuing. This question is a spin-off from this one: Swift (iOS), waiting for all images to finish downloading before returning.
I am simply trying to find out how to get dispatch_group_wait (or similar) to actually wait and not just continue before the downloads have finished. Note that if I use NSThread.sleepForTimeInterval instead of calling downloadImage, it waits just fine.
What am I missing?
class ImageDownloader {
var updateResult = AdUpdateResult()
private let fileManager = NSFileManager.defaultManager()
private let imageDirectoryURL = NSURL(fileURLWithPath: Settings.adDirectory, isDirectory: true)
private let group = dispatch_group_create()
private let downloadQueue = dispatch_queue_create("com.acme.downloader", DISPATCH_QUEUE_SERIAL)
func downloadImages(imageFilesOnServer: [AdFileInfo]) {
dispatch_group_async(group, downloadQueue) {
for serverFile in imageFilesOnServer {
print("Start downloading \(serverFile.fileName)")
//NSThread.sleepForTimeInterval(3) // Using a sleep instead of calling downloadImage makes the dispatch_group_wait below work
self.downloadImage(serverFile)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // This does not wait for downloads to finish. Why?
print("All Done!") // It gets here too early!
}
private func downloadImage(serverFile: AdFileInfo) {
let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName)
Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath }
.response { _, _, _, error in
if let error = error {
print("Error downloading \(serverFile.fileName): \(error)")
} else {
self.updateResult.filesDownloaded++
print("Done downloading \(serverFile.fileName)")
}
}
}
}
Note: these downloads are in response to an HTTP POST request and I am using an HTTP server (Swifter) which does not support asynchronous operations, so I do need to wait for the full downloads to complete before returning a response (see original question referenced above for more details).
When using dispatch_group_async to call methods that are, themselves, asynchronous, the group will finish as soon as all of the asynchronous tasks have started, but will not wait for them to finish. Instead, you can manually call dispatch_group_enter before you make the asynchronous call, and then call dispatch_group_leave when the asynchronous call finish. Then dispatch_group_wait will now behave as expected.
To accomplish this, though, first change downloadImage to include completion handler parameter:
private func downloadImage(serverFile: AdFileInfo, completionHandler: (NSError?)->()) {
let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName)
Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath }
.response { _, _, _, error in
if let error = error {
print("Error downloading \(serverFile.fileName): \(error)")
} else {
print("Done downloading \(serverFile.fileName)")
}
completionHandler(error)
}
}
I've made that a completion handler that passes back the error code. Tweak that as you see fit, but hopefully it illustrates the idea.
But, having provided the completion handler, now, when you do the downloads, you can create a group, "enter" the group before you initiate each download, "leave" the group when the completion handler is called asynchronously.
But dispatch_group_wait can deadlock if you're not careful, can block the UI if done from the main thread, etc. Better, you can use dispatch_group_notify to achieve the desired behavior.
func downloadImages(_ imageFilesOnServer: [AdFileInfo], completionHandler: #escaping (Int) -> ()) {
let group = DispatchGroup()
var downloaded = 0
group.notify(queue: .main) {
completionHandler(downloaded)
}
for serverFile in imageFilesOnServer {
group.enter()
print("Start downloading \(serverFile.fileName)")
downloadImage(serverFile) { error in
defer { group.leave() }
if error == nil {
downloaded += 1
}
}
}
}
And you'd call it like so:
downloadImages(arrayOfAdFileInfo) { downloaded in
// initiate whatever you want when the downloads are done
print("All Done! \(downloaded) downloaded successfully.")
}
// but don't do anything contingent upon the downloading of the images here
For Swift 2 and Alamofire 3 answer, see previous revision of this answer.
In Swift 3...
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
// do something, including background threads
dispatchGroup.leave()
dispatchGroup.notify(queue: DispatchQueue.main) {
// completion code
}
https://developer.apple.com/reference/dispatch/dispatchgroup
The code is doing exactly what you are telling it to.
The call to dispatch_group_wait will block until the block inside the call to dispatch_group_async is finished.
The block inside the call to dispatch_group_async will be finished when the for loop completes. This will complete almost immediately since the bulk of the work being done inside the downloadImage function is being done asynchronously.
This means the for loop finishes very quickly and that block is done (and dispatch_group_wait stops waiting) long before any of the actual downloads are completed.
I would make use of dispatch_group_enter and dispatch_group_leave instead of dispatch_group_async.
I would change your code to something like the following (not tested, could be typos):
class ImageDownloader {
var updateResult = AdUpdateResult()
private let fileManager = NSFileManager.defaultManager()
private let imageDirectoryURL = NSURL(fileURLWithPath: Settings.adDirectory, isDirectory: true)
private let group = dispatch_group_create()
private let downloadQueue = dispatch_queue_create("com.acme.downloader", DISPATCH_QUEUE_SERIAL)
func downloadImages(imageFilesOnServer: [AdFileInfo]) {
dispatch_async(downloadQueue) {
for serverFile in imageFilesOnServer {
print("Start downloading \(serverFile.fileName)")
//NSThread.sleepForTimeInterval(3) // Using a sleep instead of calling downloadImage makes the dispatch_group_wait below work
self.downloadImage(serverFile)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // This does not wait for downloads to finish. Why?
print("All Done!") // It gets here too early!
}
private func downloadImage(serverFile: AdFileInfo) {
dispatch_group_enter(group);
let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName)
Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath }
.response { _, _, _, error in
if let error = error {
print("Error downloading \(serverFile.fileName): \(error)")
} else {
self.updateResult.filesDownloaded++
print("Done downloading \(serverFile.fileName)")
}
dispatch_group_leave(group);
}
}
}
This change should do what you need. Each call to downloadImage enters the group and it doesn't leave the group until the download completion handler is called.
Using this pattern, the final line will execute when the other tasks are finished.
let group = dispatch_group_create()
dispatch_group_enter(group)
// do something, including background threads
dispatch_group_leave(group) // can be called on a background thread
dispatch_group_enter(group)
// so something
dispatch_group_leave(group)
dispatch_group_notify(group, mainQueue) {
// completion code
}
I'm trying to set up a timeout for a request method that checks username availability. When the user types in a username and presses a button, the checkUsername method is called. My code is not working because the code inside Timeout(5.0){} is never executed and timeout never gets the value false. I know this is not the best way to do it but I wanted to give it a try and wonder if this can be modified in some way or do I need a different approach?
var timeout: Bool = false
func usernameAvailable(username: String) -> String{
let response: String!
response = Server.checkUsername(username!)
Timeout(5.0){
self.timeout = true
}
while(!timeout){
if(response != nil){
return response
}
}
return "Timeout"
}
The Timeout.swift class looks like this and is working
class Timeout: NSObject{
private var timer: NSTimer?
private var callback: (Void -> Void)?
init(_ delaySeconds: Double, _ callback: Void -> Void){
super.init()
self.callback = callback
self.timer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(delaySeconds),
target: self, selector: "invoke", userInfo: nil, repeats: false)
}
func invoke(){
self.callback?()
// Discard callback and timer.
self.callback = nil
self.timer = nil
}
func cancel(){
self.timer?.invalidate()
self.timer = nil
}
}
I see what you are trying to do and it would make more sense to use an existing framework unless you really need/want to write your own networking code.
I would suggest instead to use the timeoutInterval support in an NSURLRequest along with a completion handler on NSURLSession to achieve the solution that you are seeking.
A timeout of the server response can be handled in the completion handler of something like an NSURLSessionDataTask.
Here is a working example to help get you started that retrieves data from the iTunes Store to illustrate how your timeout could be handled:
let timeout = 5 as NSTimeInterval
let searchTerm = "philip+glass"
let url = NSURL(string: "https://itunes.apple.com/search?term=\(searchTerm)")
let request: NSURLRequest = NSURLRequest(URL: url!,
cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringCacheData,
timeoutInterval: timeout)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {
(data, response, error) in
if response == nil {
print("Timeout")
} else {
print(String(data: data!, encoding: NSUTF8StringEncoding))
}
}
)
task.resume()
If you reduce the timeout interval to something short, you can force the timeout to happen.
The code in the Timeout block will never run because the timer will fire on the on the main thread, but you're blocking the main thread with your while loop.
You have another issue here, that you're calling Server.checkUsername(username!) and returning that result, which would suggest that this must be a synchronous call (which is not good). So, this is also likely blocking the main thread there. It won't even try to start the Timeout logic until checkUsername returns.
There are kludgy fixes for this, but in my opinion, this begs for a very different pattern. One should never write code that has a spinning while loop that is polling some completion status. It is much better to adopt asynchronous patterns with completionHandler closures. But without more information on what checkUsername is doing, it's hard to get more specific.
But, ideally, if your checkUsername is building a NSMutableURLRequest, just specify timeoutInterval for that and then have the NSURLSessionTask completion block check for NSError with domain of NSURLErrorDomain and a code of NSURLError.TimedOut. You also probably want to cancel the prior request if it's already running.
func startRequestForUsername(username: String, timeout: NSTimeInterval, completionHandler: (Bool?, NSError?) -> ()) -> NSURLSessionTask {
let request = NSMutableURLRequest(URL: ...) // configure your request however appropriate for your web service
request.timeoutInterval = timeout // but make sure to specify timeout
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
dispatch_async(dispatch_get_main_queue()) {
guard data != nil && error == nil else {
completionHandler(nil, error)
return
}
let usernameAvailable = ... // parse the boolean success/failure out of the `data` however appropriate
completionHandler(usernameAvailable, nil)
}
}
task.resume()
return task
}
And you can then use it like so:
private weak var previousTask: NSURLSessionTask?
func checkUsername(username: String) {
// update the UI to say that we're checking the availability of the user name here, e.g.
usernameStatus.text = "Checking username availability..."
// now, cancel prior request (if any)
previousTask?.cancel()
// start new request
let task = startRequestForUsername(username, timeout: 5) { usernameAvailable, error in
guard usernameAvailable != nil && error == nil else {
if error?.domain == NSURLErrorDomain && error?.code == NSURLError.TimedOut.rawValue {
// everything is cool, the task just timed out
} else if error?.domain == NSURLErrorDomain && error?.code != NSURLError.Cancelled.rawValue {
// again, everything is cool, the task was cancelled
} else {
// some error other happened, so handle that as you see fit
// but the key issue that if it was `.TimedOut` or `.Cancelled`, then don't do anything
}
return
}
if usernameAvailable! {
// update UI to say that the username is available
self.usernameStatus.text = "Username is available"
} else {
// update UI to say that the username is not available
self.usernameStatus.text = "Username is NOT available"
}
}
// save reference to this task
previousTask = task
}
By the way, if you do this sort of graceful, asynchronous processing of requests, you can also increase the timeout interval (e.g. maybe 10 or 15 seconds). We're not freezing the UI, so we can do whatever we want, and not artificially constrain the time allowed for the request.
I am trying to make Asynchronous call (to Parse) and during the call I DON'T want my Main UI to get hung.
Below is the function I am trying to call my ViewController. Below that function is the code I am using from my ViewController.
In the line sleep(4) line the main UI of the ViewController LoginVC does get stuck. Am I not using callbacks for the Asynchronous call correctly?
class Utils {
func logIntoWebService(uname: String, pwd: String, completion: ((result:Bool?) -> Void)!) {
PFUser.logInWithUsernameInBackground(uname, password:pwd) {
(user, error) -> Void in
if error == nil {
if user != nil {
// Yes, User Exists
//UI IS STUCK DURING THIS SLEEP(10)
sleep(10)
completion(result: true)
} else {
completion(result: false)
}
} else {
completion(result: false)
}
}
}
}
I am calling the above function from my ViewController. Below is the call from my ViewController
class LoginVC: UIViewController {
var mUtils: Utils = Utils()
mUtils.loggedInStatus({ (result) -> Void in
println("Response from logInToParse = " + String(stringInterpolationSegment: result))
})
}
You are getting confuse between background threads and completion handler.
logInWithUsernameInBackground is a async function that runs in the background however all the code that runs in the completion handler runs back in the main thread:
completion: ((result:Bool?) -> Void)!) {//The code here runs in the main thread}
So basically from the time that your application start communicate with Parse.com, until the result was back, that code was running asynchronous in a background thread, when it finish and your application received the response it runs in the completion block back in the main thread.
Now lets say you really want to run some code in the background in the completion block, than you would use something like:
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
println("Now this code will run in the background thread")
})
All the new quality of service classes are:
QOS_CLASS_USER_INTERACTIVE
QOS_CLASS_USER_INITIATED
QOS_CLASS_UTILITY
QOS_CLASS_BACKGROUND
Or in your case:
PFUser.logInWithUsernameInBackground(uname, password:pwd) {
(user, error) -> Void in
if error == nil {
if user != nil {
// Yes, User Exists
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
sleep(10)
})
completion(result: true)
} else {
completion(result: false)
}
} else {
completion(result: false)
}
}
For more information see Apple documentation