Notify when a background request has finished swift - ios

I'm sending a request to my backend server and I need to know the right way to notify my main thread the response. I'm using the NSNotificationCenter for this task but this not work sometimes and has some delay when it works because when I'm debugging I can see when the console prints the result but then a few secs later the corresponding alert to appear.
Here is my request:
let request1 = NSMutableURLRequest(URL: NSURL(string: serverID)!)
request1.HTTPMethod = "GET"
for key in parameters.keys{
request1.addValue(parameters[key] as String!, forHTTPHeaderField: key)
}
let task = NSURLSession.sharedSession().dataTaskWithRequest(request1) {
data, response1, error in
if error != nil {
print("error=\(error)")
NSNotificationCenter.defaultCenter().postNotificationName("request1Error", object: self)
return
}
else {
let httpResponse = response1 as! NSHTTPURLResponse
let headers = httpResponse.allHeaderFields as NSDictionary
let sucess: AnyObject? = headers.objectForKey("UserId")
if (sucess != nil){
let value = headers.valueForKey("info") as! String
print("info: \(value)")
NSNotificationCenter.defaultCenter().postNotificationName("sucessRequest", object: self)
}
else{
NSNotificationCenter.defaultCenter().postNotificationName("requestError2", object: self)
}
}
}
task.resume()
How should I notify my main thread the result of the request?

I don't know what exactly do you mean with main thread and if that is just a main thread (and not another controller or something similar) I think you should use closures that will get executed on main thread by using dispatch async.A function that described what I wrote would look similar like this :
func request(onSuccess : (value:String) -> Void, onError: ()->Void){
let request1 = NSMutableURLRequest(URL: NSURL(string: serverID)!)
request1.HTTPMethod = "GET"
for key in parameters.keys{
request1.addValue(parameters[key] as String!, forHTTPHeaderField: key)
}
let task = NSURLSession.sharedSession().dataTaskWithRequest(request1) {
data, response1, error in
if error != nil {
print("error=\(error)")
NSNotificationCenter.defaultCenter().postNotificationName("request1Error", object: self)
return
}
else {
let httpResponse = response1 as! NSHTTPURLResponse
let headers = httpResponse.allHeaderFields as NSDictionary
let sucess: AnyObject? = headers.objectForKey("UserId")
if (sucess != nil){
let value = headers.valueForKey("info") as! String
print("info: \(value)")
dispatch_async(dispatch_get_main_queue(),{
onSuccess(value)
})
}
else{
dispatch_async(dispatch_get_main_queue(),{
onError()
})
}
}
}
task.resume()
}

The delay is due to the fact that you're trying use the NSNotificationCenter on the same thread that you're using NSURLSession on. Try updating your calls to the NSNotificationCenter with something along the lines of:
dispatch_async(dispatch_get_main_queue()) {
NSNotificationCenter.defaultCenter().postNotificationName("sucessRequest", object: self)
}

Related

How to make dataTaskWithRequest to be chronological in swift(Xcode 9, swift 4)?

I have this question for Xcode9 Swift 4. I am trying to fetch some data from some api, and I need to use these data to display. However, since the urlSession is highly asynchronous, I cannot get the data at the right time (most of the time the data is nil). Here is the code.
func getUserInfo(){
let data = user!.Data as? [String : Any] ?? nil
if let data = data{
let ID = data["ID"] as? Int ?? nil
if let ID = ID{
let jsonUrlString = "SomeString"
let requestUrl = URL(string: jsonUrlString)
var request = URLRequest(url: requestUrl!)
request.httpMethod = "GET"
request.setValue("SomeKey", forHTTPHeaderField: "AppKey")
request.setValue(md5("Someinfo"), forHTTPHeaderField: "Sign")
dataTask = URLSession.shared.dataTask(with: request){(data, response, err) in
guard let data = data, err == nil else { // check for fundamental networking err
print("error=\(String(describing: err))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
// check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
do{
guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [Any] else{return}
var userInfo = userDetail(json: json)
let dataDic = userInfo.dataArray as? [String:Any] ?? nil
userInfo.ID = dataDic?["ID"] as? Int
userInfo.AccountName = dataDic?["AccountName"] as? String
userInfo.Avatar = dataDic?["Avatar"] as? String
} catch let jsonErr{
print(jsonErr)
}
}
dataTask?.resume()
}
}
}
I am storing the data into variable userInfo, which has properties like ID, Account Name, and Avatar. But when I call the function in another method "configNavigationBar", it cannot initialize userInfo for me.
func configNavigationBar(){
getUserInfo()
if dataTask?.state == .completed{
navigationItem.title = userInfo?.AccountName
navigationItem.setHidesBackButton(true, animated: true)
navigationController?.navigationBar.prefersLargeTitles = true
}
}
Can anybody help me with the question! I deeply appreciate any help.
How about changing title after successful http request? In a callback. You can configure everything except title before receiving data.
func configNavigationBar(){
getUserInfo { accountName in
self.navigationItem.title = accountName
}
navigationItem.setHidesBackButton(true, animated: true)
navigationController?.navigationBar.prefersLargeTitles = true
}
func getUserInfo(_ callback: #escaping (String) -> Void) {
...
var userInfo = userDetail(json: json)
let dataDic = userInfo.dataArray as? [String:Any] ?? nil
userInfo.ID = dataDic?["ID"] as? Int
userInfo.AccountName = dataDic?["AccountName"] as? String
userInfo.Avatar = dataDic?["Avatar"] as? String
// here's the insertion
callback(userInfo.AccountName)
// end of insertion
....
}
You might also need to wrap ui update into main thread if http request is using background thread.
getUserInfo { accountName in
DispatchQueue.main.async {
self.navigationItem.title = accountName
}
}
You simply need to wait until your data has been downloaded.
While download is busy you should decide to show temporary state on your navigation bar.
Then, when download is finished (or fails), you update the navigation bar again.

Table only shows when interacting and not by default

I have a table in a view controller that is populated through a dictionary from which information is retrieved via a JSON request. In the viewDidLoad() function, I call the function that retrieves the data which is added to `IncompletedDeadlines dictionary:
override func viewDidLoad() {
super.viewDidLoad()
self.IncompleteDeadlines = [String:AnyObject]()
self.retrieveIncompletedDeadlines()
}
Everything works however the table only shows when interacted with. I thought maybe the best way to show the table the moment the view appears is by adding a tableView.reload to viewDidAppear as so:
override func viewDidAppear(_ animated: Bool) {
self.tableView.reloadData()
}
But this doesn't fix it. I have attached pictures for clarity of the situation. Picture one shows the view the moment the view appears. Picture 2 only happens once the table is interacted with i.e. swiped. So my question is how can I get the table to show immediately? I understand there can be a delay because of the load, but I shouldn't have to interact with it for it to show:
When the view is interacted with i.e. swiped:
The retrieveIncompletedDeadlines() function is as so:
func retrieveIncompletedDeadlines(){
let myUrl = NSURL(string: "https://www.example.com/scripts/retrieveIncompleteDeadlines.php");
let request = NSMutableURLRequest(url:myUrl! as URL)
let user_id = UserDetails[0]
request.httpMethod = "POST";
let postString = "user_id=\(user_id)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil {
print("error=\(String(describing: error))")
return
}
var err: NSError?
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let checker:String = parseJSON["status"] as! String;
if(checker == "Success"){
let resultValue = parseJSON["deadlines"] as! [String:AnyObject]
self.IncompleteDeadlines = resultValue
}
self.tableView.reloadData()
}
} catch let error as NSError {
err = error
print(err!);
}
}
task.resume();
self.tableView.reloadData()
}
JSON will be parsed on the background thread but any update to the UI must be done on the main thread hence you have to do it inside DispatchQueue.main.async {} This article explains well what is the problem.
Furthermore I would write a completions handler which returns the data once the operation has finished. This is another interesting article about.
Completion handlers are super convenient when your app is doing something that might take a little while, like making an API call, and you need to do something when that task is done, like updating the UI to show the data.
var incompleteDeadlines = [String:AnyObject]()
override func viewDidLoad() {
super.viewDidLoad()
//please note your original function has changed
self.retrieveIncompletedDeadlines { (result, success) in
if success {
// once all the data has been parsed you assigned the result to self.incompleteDeadlines
self.incompleteDeadlines = result
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
func retrieveIncompletedDeadlines(_ completion:#escaping ([String:AnyObject] , _ success: Bool)-> Void){
let myUrl = NSURL(string: "https://www.example.com/scripts/retrieveIncompleteDeadlines.php");
let request = NSMutableURLRequest(url:myUrl! as URL)
let user_id = UserDetails[0]
request.httpMethod = "POST";
let postString = "user_id=\(user_id)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil {
print("error=\(String(describing: error))")
return
}
var err: NSError?
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let checker:String = parseJSON["status"] as! String;
var resultValue = [String:AnyObject]()
if(checker == "Success"){
resultValue = parseJSON["deadlines"] as! [String:AnyObject]
}
completion(resultValue, true)
}
} catch let error as NSError {
err = error
print(err!);
}
}
task.resume();
}
}

Xcode: URL Shared Session not running when unit testing

I have made a simple app which adds data to a database, and then retrieves it. Whilst creating unit testing, it appears that the URLSession.Shared.dataTask is not running. I can see this through the output of the print statements I have setup. Below is my code:
func addChild(childName:String,dob:String,number1:String,number2:String,parentNum:String,parentPass:String,notes:String){
//url to php file
let url = NSURL(string:"http://localhost/addChild.php")
//request to this file
let request = NSMutableURLRequest(url: url as! URL)
//method to pass data to this file
request.httpMethod = "POST"
//body to be appended to url
let body = "childName=\(childName)&dateOfBirth=\(dob)&contact1=\(number1)&contact2=\(number2)&parentAccNum=\(parentNum)&parentAccPass=\(parentPass)&notes=\(notes)"
request.httpBody = body.data(using: String.Encoding.utf8)
print("a")
//launching the request
URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { (data:Data?, response:URLResponse?, error:Error?) in
print("b")
if (error == nil){
print("c")
//send request
//get main queue in code process to communicate back to user interface
DispatchQueue.main.async(execute: {
do{
//get json result
let json = try JSONSerialization.jsonObject(with: data!,options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
print("d")
//assigning json to parseJSON in guard/secure way
//checking whether the parsing has worked
guard let parseJSON = json else{
print("Error while parsing")
return
}
//get id from parseJSON dictionary
let id = parseJSON["id"]
//if there is some id value
if id != nil{
print(parseJSON)
self.success = true
print("success")
}
}
catch{
print("Caught an error:\(error)")
}
} )
}
//if unable to proceed request
else{
print("Error:\(error)")
}
//launch prepared session
}).resume()
}
And then below is my unit testing script:
import XCTest
#testable import computerScienceCoursework
class addChildTest: XCTestCase {
//Setting up the values of the text fields
var testChildName:String = "Test name"
var testDOB:String = "99/99/99"
var testContact1:String = "00000000000"
var testContact2:String = "11111111111"
var testParAccNum:String = "-1"
var testParAccPass:String = "Password"
var testNotes:String = "Insert notes here"
var newChild = AddChildController()
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testAddChildIsWorking(){
//Assigning the values to the text fields
newChild.addChild(childName: testChildName,dob: testDOB,number1: testContact1,number2: testContact2,parentNum: testParAccNum,parentPass: testParAccPass,notes: testNotes)
XCTAssert(newChild.success == true)
}
}
Problem here is that you donĀ“t know when the async task is finished and the success property is getting updated.
There are some possible solutions for your problem one of them is to add a completion handler to your method.
func addChild(childName:String,dob:String,number1:String,number2:String,parentNum:String,parentPass:String,notes:String, completion: (Bool) -> Void){
//url to php file
let url = NSURL(string:"http://localhost/addChild.php")
//request to this file
let request = NSMutableURLRequest(url: url as! URL)
//method to pass data to this file
request.httpMethod = "POST"
//body to be appended to url
let body = "childName=\(childName)&dateOfBirth=\(dob)&contact1=\(number1)&contact2=\(number2)&parentAccNum=\(parentNum)&parentAccPass=\(parentPass)&notes=\(notes)"
request.httpBody = body.data(using: String.Encoding.utf8)
print("a")
//launching the request
URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { (data:Data?, response:URLResponse?, error:Error?) in
print("b")
if (error == nil){
print("c")
//send request
//get main queue in code process to communicate back to user interface
DispatchQueue.main.async(execute: {
do{
//get json result
let json = try JSONSerialization.jsonObject(with: data!,options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
print("d")
//assigning json to parseJSON in guard/secure way
//checking whether the parsing has worked
guard let parseJSON = json else{
print("Error while parsing")
completion(false)
return
}
//get id from parseJSON dictionary
let id = parseJSON["id"]
//if there is some id value
if id != nil{
print(parseJSON)
self.success = true
print("success")
completion(true)
}
}
catch{
print("Caught an error:\(error)")
completion(false)
}
} )
}
//if unable to proceed request
else{
print("Error:\(error)")
completion(false)
}
//launch prepared session
}).resume()
}
Then in your test method you can the method.
func testAddChildIsWorking()
{
let asyncExpectation = expectationWithDescription("addChildIsWorkingFunction")
newChild.addChild(childName: testChildName, dob: testDOB, number1: testContact1,
number2: testContact2, parentNum: testParAccNum, parentPass: testParAccPass, notes: testNotes) { (success) in
asyncExpectation.fulfill()
}
self.waitForExpectationsWithTimeout(10) { error in
XCTAssert(newChild.success == true)
}
}
waitForExpectationWithTimeout is waiting until a fulfill is trigger or a timeout occurs. In this way you could test your async code.
For more informations check this link
Hope that helps.

DispatchQueue.main.sync returning exc_bad_instruction Swift 3

I want to display an ActivityIndicatorView in my app, but when I call the sync method from the main thread, the app crashes with the error: exc_bad_instruction (code=exc_i386_invop subcode=0x0)
I'm using xcode 8.0 and swift 3
Can someone please help me?
func POST(endpoint:NSString!,body:NSString!,vc:UIViewController? = nil)->NetworkResult{
let result = NetworkResult()
DispatchQueue.main.sync {
self.displayActivityIndicator(viewController: vc)
}
let urlStr = self.url.appending(endpoint as String).appending(self.getHashAutenticacao() as String)
print(urlStr)
let request = NSMutableURLRequest(url: URL(string: urlStr)!, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 20)
print(request.debugDescription)
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = body.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: true)
// send the request
var data: NSData!
do {
data = try NSURLConnection.sendSynchronousRequest(request as URLRequest, returning: &self.response) as NSData!
} catch let error1 as NSError {
self.error = error1
data = nil
}
if let httpResponse = self.response as? HTTPURLResponse {
result.resultCode = httpResponse.statusCode
if httpResponse.statusCode == 200{
if data != nil{
if data.length > 0{
let json = (try! JSONSerialization.jsonObject(with: data as Data, options: JSONSerialization.ReadingOptions.mutableContainers))
if let jsonArray:NSArray = json as? NSArray{
result.data = jsonArray
}else{
if let jsonDict:NSDictionary = json as? NSDictionary{
result.data = [jsonDict]
}
}
}
}
}
}else {
result.message = self.error!.debugDescription as NSString?
}
DispatchQueue.main.sync {
self.hideActivityIndicator(viewController: vc)
}
return result
}
What you are trying to do here is to launch the main thread synchronously from a background thread before it exits. This is a logical error.
You should do it like this:
DispatchQueue.global().async(execute: {
print("teste")
DispatchQueue.main.sync{
print("main thread")
}
})
Maybe it's because you are trying to DispatchQueue.main.sync from the Main thread. You can check if you are already in the main thread like:
if Thread.isMainThread {
// do stuff
} else {
DispatchQueue.main.sync {
// do stuff
}
}
If you want to read the value from the main thread, here is a handy function:
func syncMain<T>(_ closure: () -> T) -> T {
if Thread.isMainThread {
return closure()
} else {
return DispatchQueue.main.sync(execute: closure)
}
}
Usage:
// we are in BG or Main, does not matter
let value = syncMain {
// we are in Main for sure
return ...
}

Making a re-useable function of JSON URL fetching function in SWIFT 2.0

I am stuck in a problem. I think it is all due to my weak basics. I am sure someone can help me easily and put me in the right direction.
I have different segues and all get the data from JSON via remote URL.
So in-short all segues need to open URL and parse JSON and make them into an ARRAY
I have made the first segue and it is working fine.
Now i plan to use the functions where it download JSON and turns it into ARRAY as a common function
I read in another page on stackoverflow that I can declare all common functions outside the class in ViewController
I hope everyone is with me this far.
now in ViewController i declare a function
getDataFromJson(url: String)
This function code looks like following
func getJsonFromURL(url: String)
{
// some class specific tasks
// call the common function with URL
// get an array
let arrJSON = getJsonArrFromURL(url)
for element in arrJSON
{
// assign each element in json to ur table
print("Element: \(element)")
}
// some class specific tasks
}
and this will call the common function declared outside the score of class
getArrFromJson(url: String) -> NSArray
This common function is just very generic.
Take a URL, call it, open it, parse its data into ARRAY and return it back.
The problem i am stuck is where to put the return
It returns empty array as the task is not finished and i am clueless
func getJsonArrFromURL(var url: String) -> NSArray
{
var parseJSON : NSArray?
if ( url == "" )
{
url = self.baseURLHomepage
}
print("Opening a JSON URL \(url)")
let myUrl = NSURL(string: url);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "GET";
let postString = "";
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
if ( error != nil )
{
print("Error open JSON url \n\(error)")
return
}
do
{
parseJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
}
catch
{
self.showAlert("Error", msg: "Error occurred while trying to process the product information data")
print("Error occured in JSON = \(error)")
}
}
task.resume()
return parseJSON!
}
You can probably add a method like below in any of your class
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) -> NSURLSessionTask {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "GET"
let bodyData = info
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
dispatch_async(dispatch_get_main_queue()) {
guard data != nil else {
print("response String is nil")
completionHandler(nil, error)
return
}
if let dataNew = data {
completionHandler(NSString(data: (NSData(base64EncodedData: dataNew, options: NSDataBase64DecodingOptions([])))!, encoding: NSASCIIStringEncoding), nil)
}
}
}
task.resume()
return task
}
and access it anywhere like
let url = "your URL String"
let info = "The data you would like to pass"
yourClassName.post(url, info: info) { responseString, error in
guard responseString != nil else {
print("response String is nil")
print(error)
return
}
do {
if !(responseString as? String)!.isEmpty {
let json = try NSJSONSerialization.JSONObjectWithData((responseString as! String).data, options: NSJSONReadingOptions.init(rawValue: 0))
//process your json here
}
} catch {
print("Error\n \(error)")
return
}
}
Extend your string like follows
extension String {
var data:NSData! {
return dataUsingEncoding(NSUTF8StringEncoding)
}
}

Resources