just wanted some clarification on the best practices to make network api calls in Swift 2.
Here is how my typical network requests looks like to download JSON data:
let session = NSURLSession(configuration: .defaultSessionConfiguration())
let url = NSURL(string: my_url_string)
let request = NSURLRequest(URL: url)
let dataTask = session.dataTaskWithRequest(request) { data, response, error in
do {
self.tableData = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! [NSDictionary]
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
} catch let error {
print(error)
}
}
dataTask.resume()
My question is: should I wrap all of this code block into a background queue? Should I do as follows:
let download_queue = dispatch_queue_create("download", nil)
dispatch_async(download_queue) { () -> Void in
previous code here
}
Or should I use one of the given high priority queues such as:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)
Also, when making additional network requests on subsequent view controllers, should I use the same queue I use here or should I create a new one?
By default NSURLSession API is highly asynchronous. Usefull information from the Apple docs.
There is no visible issues that indicate to wrap you're code block with GCD and also completion block runs on background thread so there is right usage of the GCD to update UITableview
Related
I am unsure about the thread/queue control in Swift for HTTP methods.
Working on the part of my app that makes GET requests, as well as POST and others, from a 3rd party server.
From my main UIViewController, I initialize the class with:
let uploadService = UploadService()
lazy var uploadSession: URLSession = {
let configuration = URLSessionConfiguration.default
return URLSession(configuration: configuration, delegate: self, delegateQueue: .main)
}()
Then later on I call it from the same UIViewController, let uploadService = UploadService()
Before I start to add more functionality to the class below I wanted to ask about:
return URLSession(configuration: configuration, delegate: self, delegateQueue: .main)
When the UploadService class calls methods to my UIViewController, Xcode warned me to do it from the main thread/queue, which I do. So my questions is, does Swift automatically move anything HTTP related to the non-main thread/queue so I don't have to worry about it? Because if that's the case, great. But then why ask me to place it in the main thread/queue, as I did up above? (which I did from a tutorial).
Just want to be clear, should I be declaring it the background thread/queue, or does Swift handle that for me regardless of how I declared it up above?
class UploadService {
var uploadSession = URLSession.shared
func start(upFile: XFile, script: String, upLoadInvoiceClass: UploadInvoice) {
var request = upFile.makeUrlReq(upFile: upFile, script: script)
uploadSession.uploadTask(with: request, from: request.httpBody )
{ (data, response, error) in
if let response = response {
upLoadoiceClass.upResp(resp: response)
}
if let error = error {
upLoadoiceClass.upErr(error: error)
}
if let data = data {
upLoadoiceClass.upData(data: data)
}
}.resume()
}
}
URLSession ALWAYS does network interactions on a background thread. You don't have to worry about that.
The completion handlers/delegate methods are also run on a background thread by default. That lets you do time-consuming processing on the resulting data without tying up the main thread.
Unless you point give your URL session a different delegate queue when you create it, you should wrap any UI code you put in your completion handlers/delegate methods in a call to the main thread.
Alternately, you can create a URLSession and give it a foreground queue in the delegateQueue parameter you pass in the initializer. If you do that then your completion handlers/delegate methods will be run on that foreground queue (and thus run on the main thread.) If you do that you don't need to explicitly wrap UIKit calls in a call to the main thread, but you will stall the UI if you do time-consuming work in your URLSession completion handlers/delegate methods
I am following Stanfords' CS193p Developing Apps for iOS online course.
It is using the Grand Central Dispatch (GCD) API for a demo of multithreading.
But they noted, that
"GCD has been mostly replaced by Swift's new built-in async API as of WWDC 2021".
So I wanted to learn how the code from the Lecture would look like after updating it to use this new API.
After watching Apple's WWDC videos, it seems to me like
DispatchQueue.global(qos: .userInitiated).async { } is replaced in this new async API with Task { } or Task(priority: .userInitiated) {}, but I'm not sure, what has DispatchQueue.main.async { } been replaced with?
So, my questions are:
Am I correctly assuming, that DispatchQueue.global(qos: .userInitiated).async { } has been replaced with Task(priority: .userInitiated) {}
What has DispatchQueue.main.async { } been replaced with?
Please help, I want to learn this new async-await API.
Here's the code from the Lecture, using old GCD API:
DispatchQueue.global(qos: .userInitiated).async {
let imageData = try? Data(contentsOf: url)
DispatchQueue.main.async { [weak self] in
if self?.emojiArt.background == EmojiArtModel.Background.url(url) {
self?.backgroundImageFetchStatus = .idle
if imageData != nil {
self?.backgroundImage = UIImage(data: imageData!)
}
// L12 note failure if we couldn't load background image
if self?.backgroundImage == nil {
self?.backgroundImageFetchStatus = .failed(url)
}
}
}
}
The whole function (in case you need to see more code):
private func fetchBackgroundImageDataIfNecessary() {
backgroundImage = nil
switch emojiArt.background {
case .url(let url):
// fetch the url
backgroundImageFetchStatus = .fetching
DispatchQueue.global(qos: .userInitiated).async {
let imageData = try? Data(contentsOf: url)
DispatchQueue.main.async { [weak self] in
if self?.emojiArt.background == EmojiArtModel.Background.url(url) {
self?.backgroundImageFetchStatus = .idle
if imageData != nil {
self?.backgroundImage = UIImage(data: imageData!)
}
// L12 note failure if we couldn't load background image
if self?.backgroundImage == nil {
self?.backgroundImageFetchStatus = .failed(url)
}
}
}
}
case .imageData(let data):
backgroundImage = UIImage(data: data)
case .blank:
break
}
}
If you really are going to do something slow and synchronous, Task.detached is a closer analog to GCD’s dispatching to a global queue. If you just use Task(priority: ...) { ... } you are leaving it to the discretion of the concurrency system to decide which thread to run it on. (And just because you specify a lower priority does not guarantee that it might not run on the main thread.)
For example:
func fetchAndUpdateUI(from url: URL) {
Task.detached { // or specify a priority with `Task.detached(priority: .background)`
let data = try Data(contentsOf: url)
let image = UIImage(data: data)
await self.updateUI(with: image)
}
}
And if you want to do the UI update on the main thread, rather than dispatching it back to the main queue, you would simply add the #MainActor modifier to the method that updates the UI:
#MainActor
func updateUI(with image: UIImage?) async {
imageView.image = image
}
That having been said, this is a pretty unusual pattern (doing the network request synchronously and creating a detached task to make sure you don't block the main thread). We would probably use URLSession’s new asynchronous data(from:delegate:) method to perform the request asynchronously. It offers better error handling, greater configurability, participates in structured concurrency, and is cancelable.
In short, rather than looking for one-to-one analogs for the old GCD patterns, use the concurrent API that Apple has provided where possible.
FWIW, in addition to the #MainActor pattern shown above (as a replacement for dispatching to the main queue), you can also do:
await MainActor.run {
…
}
That is roughly analogous to the dispatching to the main queue. In WWDC 2021 video Swift concurrency: Update a sample app, they say:
In Swift’s concurrency model, there is a global actor called the main actor that coordinates all operations on the main thread. We can replace our DispatchQueue.main.async with a call to MainActor’s run function. This takes a block of code to run on the MainActor. …
But he goes on to say:
I can annotate functions with #MainActor. And that will require that the caller switch to the main actor before this function is run. … Now that we've put this function on the main actor, we don’t, strictly speaking, need this MainActor.run anymore.
Does promises always run on background thread.
#IBAction func doNetworkCall(_ sender: Any) { // Does this run on
background thread
Network.fetchPhotos().done { (photos) in
}
}
static func fetchPhotos () -> Promise<[Photo]> {
return Promise { seal in
AF.request("https:photosURL", method: .post, parameters: ["auth":"1231","user_id":"u12312"]).responseJSON { (response) in
guard let data = response.data else { return }
let coder = JSONDecoder()
let photos = try! coder.decode([Photo].self, from: data)
seal.fulfill(photos)
}
}
}
I have used promises with purpose of all network calls runs on background thread irrespective calling from main thread. I have some chain network requests which will be easier to implement.
is this assumption correct?
Alamofire always runs its requests on a background queue. In your example the only part that isn't in the background is the responseJSON closure. By default that closure runs on the .main queue. I recommend you adopt responseDecodable to decode your responses so that the parsing is also in the background only call fulfill the promise in the closure. (I'm not sure whether fulfilling on the main queue is otherwise necessary.)
In order to fully render my View from my View Controller, I need to have a response from a network request.
I have been trying to do this is many different ways, but have been unsuccessful each time.
Originally, I had gotten it to work by making a "synchronous" network request prior to calling any methods to render the View. However, the compiler is warning me that the synchronous network requests are deprecated as of ios 8.
What is the best way to accomplish this in the most performant way?
I have tried:
override func loadView() {
dispatch_sync(dispatch_get_main_queue()){
// GET the Markup
let url = NSURL(string: self.PageURL)
let request = NSURLRequest(URL: url!)
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let RequiredViewData = session.dataTaskWithRequest(request) {(data, response, error) in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
self.RequiredViewJSON = json
self.view = UIView(frame: UIScreen.mainScreen().bounds)
print(data)
} catch {
print("error serializing JSON: \(error)")
}
}
RequiredViewData.resume()
}
}
But that just makes my app render as a blank screen.
Essentially what I need to accomplish is this:
Make a network request and receive the response before any view rendering can occur.
Thanks in Advance!
I never really tried to override loadView nor know if you should, but I think what you need to do is call super in this case to get your view to render again.
Edit
Also per your comment I put the main thread call "after" you get the call back from the NSURLSession. I might have a } in the wrong spot but should get you close enough.
override func loadView() {
// GET the Markup
let url = NSURL(string: self.PageURL)
let request = NSURLRequest(URL: url!)
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let RequiredViewData = session.dataTaskWithRequest(request) {(data, response, error) in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
self.RequiredViewJSON = json
dispatch_sync(dispatch_get_main_queue()){
//You may also want to try commenting this out unless you are intentionally creating a blank view.
self.view = UIView(frame: UIScreen.mainScreen().bounds)
print(data)
//call super after you get what you need
super.loadView()
}
} catch {
print("error serializing JSON: \(error)")
}
}
RequiredViewData.resume()
}
}
Hopefully that helps.
The view controller should handle all of this networking logic in viewDidLoad or viewWillAppear, not in loadView. I'd suggest setting a loading state an initial empty state on the subview, then once you have what you need, update the view with that data. You may need to call setNeedsLayout on the view to update for the new data.
I have a very big problem. I use Parse cloud system. When i fetch images with using "dispatch_semaphore" from parse, the main thread is locked. However, i think, i don't use main thread when fetching images. Normally, the task should be executed order by section A,B,C then D but app locked in section B.
Thanks.
let semaphore1:dispatch_semaphore_t = dispatch_semaphore_create(0)
let semaphore2:dispatch_semaphore_t = dispatch_semaphore_create(0)
let userquery = PFQuery(className: "_User")
userquery.findObjectsInBackground().continueWithSuccessBlock { (task) -> AnyObject? in
let results = task.result as! NSArray
for objectarray in results
{
let object = objectarray as! PFObject
let username = object["username"] as! String
let userpictureThumbnail = object["userPhotoThumbnail"] as! PFFile
userpictureThumbnail.getDataInBackground().continueWithSuccessBlock({ (task2) -> AnyObject? in
let result = task2.result as! NSData
let image = UIImage(data: result)
let imageThumbnail = image
// Section C-) Below codes must be executed but main thread is locked by Section B.
Model.sharedInstance.friendsPictureModel.addItem(username,FriendImageThumbnail:imageThumbnail!)
dispatch_semaphore_signal(semaphore2)
return nil
})
// Section B-) Second, enter the below code . And lock main thread then app freezed.
dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER)
}
dispatch_semaphore_signal(semaphore1)
return nil
}
// Section A-) When the block("userquery.findObjectsInBackground().continueWithSuccessBlock") is executed, enter the below code firstly.
dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER)
// Section D-) Below codes must be executed in the last.
self.collectionview.reloadData()
You should not be using semaphores here. Your section A is (presumably) running on the main thread, and the wait will cause it to block until the semaphore is signalled.
You could remove all the semaphore code and just dispatch self.collectionview.reloadData() onto the main thread in where semaphore2 is currently being signalled. However, you also have a problem that addItem is being called in the background, and it's probably not thread safe.
On the assumption that your example is a simplification of your specific problem, you probably have some separation between the viewController and userquery (let's call it findTheObjects. So at the moment you would have something like:
myObjectFinder.findTheObjects()
In this case, you should pass in your own completion block, along the lines of:
myObjectFinder.findTheObjects(completion: {
(username, imageThumbnail?) -> Void in
dispatch_async(dispatch_get_main_queue(), {
// do something with the results like...
Model.sharedInstance.friendsPictureModel.addItem(username,FriendImageThumbnail:theResults.imageThumbnail!)
self.collectionview.reloadData()
})
}
This completion block would then be called from your Section C.