Swift: Ensure urlSession.dataTask is completed in my function before passing result - swift

Hello I have this function:
func planAdded(id:Int, user_id:Int) -> Int {
let locationURL = "myurl"
var planResult: Int = 0
let request = URLRequest(url: URL(string: locationURL)!)
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: request, completionHandler:{
(data, response, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print (error)
return
}
if let data = data {
let responseString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
planResult = responseString!.integerValue
}
}
})
task.resume()
print(planResult)
return planResult
}
What I am trying to do is to ensure that I got the result for planResult in tableView cellforrow at indexpath function.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
case 4:
if (result == 1){
...
} else if (result == 2){
...
} else {
...
}
default:
cell.fieldLabel.text = ""
}
return cell
}
Here is my viewDidLoad function
override func viewDidLoad() {
super.viewDidLoad()
self.result = self.planAdded(1, 2)
}
For some reasons, this keeps returning 0; however, the print line is actually printing correct value. I did some research and I believe this is because of asychonous call of the dataTask. Is there a way I ensure that my function is actually completed and return the value for the indexpath function?
Thanks

The reason is, you are doing it in a wrong way! Because, once you intialize the class the UIViewController lifecycle starts. Once the viewDidLoad() is called it the UITableView is also updated with no data.
Also, you are calling API to get the data, you need to notify UITableViewDataSource to update data and here is how you can do that!
func planAdded(id:Int, user_id:Int) {
let locationURL = "myurl"
var planResult: Int = 0
let request = URLRequest(url: URL(string: locationURL)!)
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: request, completionHandler:{
(data, response, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print (error)
return
}
if let data = data {
let responseString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
self.result = responseString!.integerValue
self.tableView.reloadData()
}
}
})
task.resume()
}
And you are getting zero value because it's an async method. So get the data you need to use completionCallback.
func planAdded(id:Int, user_id:Int, completion: (result: Int) -> ()) {
let locationURL = "myurl"
var planResult: Int = 0
let request = URLRequest(url: URL(string: locationURL)!)
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: request, completionHandler:{
(data, response, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print (error)
return
}
if let data = data {
let responseString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
planResult = responseString!.integerValue
completion(planResult)
}
}
})
task.resume()
}
Usage:
override func viewDidLoad() {
super.viewDidLoad()
planAdded(1, 2){(value) in
self.result = value
self.tableView.reloadData()
}
}

Related

Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread in swift

I have given DispatchQueue.main.async {} where it necessary but when i give break point from dataTask here it says
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.'
Cannot be called with asCopy = NO on non-main thread.
class EventsViewController: UIViewController {
#IBOutlet weak var backBtn: UIButton!
var eventsListArray = [AnyObject]()
var eventType: String?
var eventList : EventsModel? = nil
#IBOutlet weak var eventsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getAllEventsList()
}
func getAllEventsList() {
DispatchQueue.main.async {
let deviceId: String = (UIDevice.current.identifierForVendor?.uuidString)!
let personalId: String = UserDefaults.standard.string(forKey: "regUserID") ?? ""//KeychainWrapper.standard.string(forKey: "USERID") ?? ""
let headers = ["deviceid": deviceId,"userType": "personal","key": personalId]
DispatchQueue.main.async {
let string = Constants.GLOBAL_URL + "/get/allevents"
var urlComponents = URLComponents(string: string)
let eventStatus = self.eventType
let requestEventType = URLQueryItem(name: "eventstatus", value: eventStatus)
urlComponents?.queryItems = [requestEventType]
let urlStr = urlComponents?.url
let request = NSMutableURLRequest(url: urlStr!, cachePolicy: .useProtocolCachePolicy,timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers as! [String : String]
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if error == nil {
let httpResponse = response as? HTTPURLResponse
if httpResponse!.statusCode == 200 {
do {
let jsonObject = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as! [String :AnyObject]
print("publish event \(jsonObject)")
self.eventList = EventsModel.init(fromDictionary: jsonObject)
DispatchQueue.main.async {
if self.eventList?.events.count != 0 {
DispatchQueue.main.async {
self.eventsTableView.reloadData()
}
}
else {
DispatchQueue.main.async {
Constants.showAlertView(alertViewTitle: "", Message: "No Events \(self.eventType)", on: self)
self.eventList?.events.removeAll()
self.eventsTableView.reloadData()
}
}
}
} catch { print(error.localizedDescription) }
} else {
Constants.showAlertView(alertViewTitle: "", Message: "Something went wrong, Please try again", on: self)
}
}
})
dataTask.resume()
}
}
}
}
You've probably missed a few spots where you're trying to present an alert when errors are thrown. Why don't you just enter the main queue right after the data request is complete.
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
DispatchQueue.main.async {
if error == nil {
//...
}
}
})

Completion Handler True before completed

so I have a function that gets a quote and author from an API. I have a completion handler so that i can get the quote and author and then set them to their respective UILabel in the Viewdidload function. But for some reason both the quote and author come up nil. What's going wrong with the handler?
func getJSON(completionHandler: #escaping(CompletionHandler)){
if let quoteURL = URL(string: "http://quotes.rest/qod.json")
{
let session = URLSession.shared
let task = session.dataTask(with: quoteURL)
{ (data, response, error) -> Void in
if data != nil
{
let quoteData = JSON(data: data!)
self.quote = quoteData["contents"]["quotes"][0]["quote"].stringValue
self.author = quoteData["contents"]["quotes"][0]["author"].stringValue
}
}
task.resume()
}
completionHandler(true)
}
Calling the function in the Viewdidload()
self.getJSON(completionHandler: {(success)-> Void in
if(success){
self.quoteLabel.text = "\(self.quote ?? "") - \(self.author ?? "")"
}
})
Swift doesn't allow you to set UILabel text in background processes which is why i cannot do it in getJSON()
Thanks
You need to insert it inside the callback
func getJSON(completionHandler: #escaping(CompletionHandler)){
if let quoteURL = URL(string: "http://quotes.rest/qod.json")
{
let session = URLSession.shared
let task = session.dataTask(with: quoteURL)
{ (data, response, error) -> Void in
if data != nil
{
let quoteData = JSON(data: data!)
self.quote = quoteData["contents"]["quotes"][0]["quote"].stringValue
self.author = quoteData["contents"]["quotes"][0]["author"].stringValue
completionHandler(true) // set it inside the callback
}
else {
completionHandler(false)
}
}
task.resume()
}
else {
completionHandler(false)
}
}

Accessing a variable outside a given function

I tried the completion handler as suggested by Gurdev and got something. It does return the values of my array in the MAIN function. But the issue is the same: I have to use Sleep() function for 3 seconds for the HTTP request to complete. This is like hard coding as it could take less or more than 3 seconds to complete. When I remove the Sleep() command, I end up returning a VOID array.
The relevant code is pated below.
Thanks!
--------Web Class--------
import Foundation
import UIKit
class Web {
var ar1 = [Double]()
var ar2 = [Double]()
func getData(str: String, completion: (_ result: [[Double]]) -> Void) {
let request = NSMutableURLRequest(url: URL(string: str)!)
httpGet(request as URLRequest!){
(data, error) -> Void in
if error != nil {
print(error ?? "Error")
} else {
let delimiter = "\t"
let lines:[String] = data.components(separatedBy: CharacterSet.newlines) as [String]
for line in lines {
var values:[String] = []
if line != "" {
values = line.components(separatedBy: delimiter)
let str1 = (values[0])//FIRST COLUMN
let str2 = (values[1])//SECOND COLUMN
let db1 = NumberFormatter().number(from: str1)?.doubleValue
self.ar1.append(db1!)
let db2 = NumberFormatter().number(from: str2)?.doubleValue
self.ar2.append(db2!)
}
}
}
}//end of request
sleep(3) // This delay sends data to MAIN, Otherwise NOT
let dta = [self.ar1, self.ar2]
completion(dta)
}
func httpGet(_ request: URLRequest!, callback: #escaping (String, String?) -> Void) {
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if error != nil {
callback("", error!.localizedDescription)
} else {
let result = NSString(data: data!, encoding:
String.Encoding.ascii.rawValue)!
callback(result as String, nil)
}
})
task.resume()
}
}
--------Web Class--------
-------Call In Class--------
Web().getData (str: "https://dl.dropboxusercontent.com/s/f6j7w7zeqaavzqw/s02.txt?dl=0")
{
(result: [[Double]]) in
let x = result[0]
let y = result[1]
}
-------Call In Class--------
Essentially, I am trying to access my variable "ar1" at a certain point in my code but I cannot. I am trying to return the value too but it returns NULL at that point.
What is wrong here ?
I tried like the below code :
import Foundation
import UIKit
class Web {
var ar1 = [Double]()
var ar2 = [Double]()
func getData(str: String) -> [Double] {
let request = NSMutableURLRequest(url: URL(string: str)!)
httpGet(request as URLRequest!){
(data, error) -> Void in
if error != nil {
print(error ?? "Error")
} else {
let delimiter = "\t"
let lines:[String] = data.components(separatedBy: CharacterSet.newlines) as [String]
for line in lines {
var values:[String] = []
if line != "" {
values = line.components(separatedBy: delimiter)
let str1 = (values[0])//FIRST COLUMN
let str2 = (values[1])//SECOND COLUMN
let db1 = NumberFormatter().number(from: str1)?.doubleValue
self.ar1.append(db1!)
let db2 = NumberFormatter().number(from: str2)?.doubleValue
self.ar2.append(db2!)
}
}
dump (self.ar1) // "ar1" accessible HERE (returns all elements)
}
}//end of request
//BUT I WANT TO RETURN "ar1" HERE !
// So that I can use it in my MAIN class
dump (self.ar1) // "ar1" not accessible here (returns ZERO elements)
return self.ar1
}
func httpGet(_ request: URLRequest!, callback: #escaping (String, String?) -> Void) {
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if error != nil {
callback("", error!.localizedDescription)
} else {
let result = NSString(data: data!, encoding:
String.Encoding.ascii.rawValue)!
callback(result as String, nil)
}
})
task.resume()
}
}
In your code you are returning the ar1 from the return statement of your method whereas the value to ar1 is being set in Aysnchronous callback completion handler. You should use the completion handler block where you are trying to access the value of ar1. HTTPGet function is running in Async mode and value in ar1 is set in the callback handler block.
Let me know if you need any further help on this.

Return function after downloading all images in array [AlamofireImage]

I currently have a function with a completionHandler which should return the count of download images from an array but unfortunately the function returns too early and the count is always 0 (It seems the completionHandler value is being returned before the function is complete. How can I fix this?)
func loadFunctions(){
//Append results to data array
getData{sta in
if (sta == 0){
self.downloadImages{ handler in
print(handler)
}
}
}
}
func downloadImages (completionHandler: (Int) -> ()) -> () {
for index in data {
let URLRequest = NSURLRequest(URL: NSURL(string: index.artworkUrl512)!)
downloader.downloadImage(URLRequest: URLRequest) { response in
if let image = response.result.value {
cardImages.append(image)
print(image)
}
}
}
completionHandler(cardImages.count)
}
Function using semaphore
func downloadImages (completionHandler: (Int) -> ()) -> () {
let semaphore = dispatch_semaphore_create(0)
for index in data {
dispatch_semaphore_signal(semaphore)
let URLRequest = NSURLRequest(URL: NSURL(string: index.artworkUrl512)!)
downloader.downloadImage(URLRequest: URLRequest) { response in
if let image = response.result.value {
cardImages.append(image)
print(image)
}
}
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
completionHandler(cardImages.count)
}

NSURLSession Response String completion block - Swift

I want to wait for a responseString to complete before calling the next function "nextScreen()" (segue). At the moment I have an if statement to make sure it is not nil before proceeding, but sometimes the the next function/segue is called because the responseString is still downloading.
Could you help with a completion block? I have found completion blocks for NSURLSession, but these just wait for the initial HTTP call to complete, not the response string.
func getProfiles(){
func post(completion: (message: String?) -> Void) {
let request = NSMutableURLRequest(URL: NSURL(string: "http://**.**.**.**/EPG/XML/QueryProfile")!)
request.HTTPMethod = "POST"
let postString = "<QueryProfileReq><type>1</type></QueryProfileReq>"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task: Void = NSURLSession.sharedSession().dataTaskWithRequest(request,
completionHandler: {(data: NSData!,
response: NSURLResponse!,
error: NSError!) in
if error != nil {
println("error=\(error)")
let alert = UIAlertView()
alert.delegate = self
alert.title = "Login Error"
alert.message = "\(error)"
alert.addButtonWithTitle("OK")
alert.show()
self.view.endEditing(true)
return
}
if let responseString = NSString(data: data, encoding: NSUTF8StringEncoding) {
if response != nil {
println("got profiles")
self.nextScreen()
}
self.dataVar = data // UPDATES VARIABLE TO SEND
}
}).resume()
}
}
The convenience method of dataTaskWithRequest essentially returns data or error, with usually some response header type information. If you have an error then you won't have data (99% sure about this). I have re formatted your method to help. The NSString Init Convenience method is synchronous so not quite sure by what you mean by waiting to complete instead of http call?
func getStringFromRequest(completionHandler:(success:Bool, data: NSData?) -> Void) {
let request = NSMutableURLRequest(URL: NSURL(string: "http://##.##.##.##/EPG/XML/QueryProfile")!)
request.HTTPMethod = "POST"
let postString = "<QueryProfileReq><type>1</type></QueryProfileReq>"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) -> Void in
if let unwrappedError = error {
print("error=\(unwrappedError)")
}
else {
if let unwrappedData = data {
completionHandler(success: true, data: unwrappedData)
return
}
}
completionHandler(success: false, data: nil)
}
task?.resume()
}
func performPost() {
getStringFromRequest { (success, data) -> Void in
if (success) {
if let unwrappedData = data {
self.dataVar = unwrappedData
if let responseString = NSString(data: unwrappedData, encoding: NSUTF8StringEncoding) {
self.nextScreen()
}
}
}
else {
print("Failed")
}
}
}