Swift 3: URLSession / URLRequest Not Working - ios

I am still trying to convert our application from Swift 2 over to Swift 3 because I am being forced to since all of our Apple devices are now running iOS 10.
I have gone through the code conversion and thought I was doing well however, while attempting to debug my JSON issues (posted in another question), I am now dealing with requests not even being sent.
let params: [String:AnyObject] = [
"email":"\(self.preferences.string(forKey: "preference_email")!)" as AnyObject
]
let requestParams: [String:AnyObject] = [
"action":"601" as AnyObject,
"params":params as AnyObject
]
do {
let requestObject = try JSONSerialization.data(withJSONObject: requestParams, options:[])
var request = URLRequest(url: URL(string: "http://domain.tld/path/")!)
request.httpBody = requestObject
request.httpMethod = "POST"
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
NSLog("Got here?")
session.dataTask(with: request) {data, response, error in
guard let data = data, error == nil else {
print("error=\(error)")
return
}
NSLog("Got here 3?")
let object:JSON = JSON(data:data)
NSLog("Object: \(object)")
}.resume()
NSLog("Got here 4?")
} catch {
NSLog("Got here catch?")
}
NSLog("End of getUser")
The code above yields the following output:
2016-10-04 13:00:12.011969 OneTouch[1589:623015] [DYMTLInitPlatform] platform initialization successful
2016-10-04 13:00:12.264319 OneTouch[1589:622954] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2016-10-04 13:00:12.265321 OneTouch[1589:622954] [MC] Reading from public effective user settings.
2016-10-04 13:00:12.295055 OneTouch[1589:622954] Got here?
2016-10-04 13:00:12.295445 OneTouch[1589:622954] Got here 4?
2016-10-04 13:00:12.295515 OneTouch[1589:622954] End of getUser
(lldb)
Which means that the request isn't even being made. Is there some key that I have to add to the PLIST again? This is starting to get annoying.
Below is my old code and it isn't even working anymore:
let params: [String:AnyObject] = [
"email":"\(self.preferences.string(forKey: "preference_email")!)" as AnyObject
]
let requestParams: [String:AnyObject] = [
"action":"601" as AnyObject,
"params":params as AnyObject
]
do {
let requestObject = try JSONSerialization.data(withJSONObject: requestParams, options:[])
let request = NSMutableURLRequest(url: URL(string: "http://domain.tld/path/" as String)!, cachePolicy:NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 20)
request.httpBody = requestObject
request.httpMethod = "POST"
NSLog("Got here?")
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {data, response, error in
if error != nil {
NSLog("Got here 2?")
}
NSLog("Got here 3?")
let object:JSON = JSON(data:data!)
NSLog("Object: \(object)")
})
NSLog("Got here 4?")
task.resume()
} catch {
NSLog("Got here catch?")
}
NSLog("End of getUser")
The code above yields the same output as the other code does!

If you put a breakpoint immediately after calling getUser, the URLSession task's completion handler, which runs asynchronously (i.e. generally finishes later, unless the request failed immediately or was satisfied by some cached response) may not have had a chance to be called.
If you put a breakpoint inside the dataTask completion handler, you should see your data at that point.
Personally, I'd make sure to give getUser a completion handler so you know when it's done:
func getUser(completionHandler: #escaping (JSON?, Error?) -> Void) {
let params = [
"email":"\(preferences.string(forKey: "preference_email")!)"
]
let requestParams: [String: Any] = [
"action": "601",
"params": params
]
do {
let requestObject = try JSONSerialization.data(withJSONObject: requestParams)
var request = URLRequest(url: URL(string: "http://domain.tld/path/")!, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 20)
request.httpBody = requestObject
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) {data, response, error in
guard let data = data, error == nil else {
completionHandler(nil, error)
return
}
completionHandler(JSON(data: data), nil)
}
task.resume()
} catch {
completionHandler(nil, error)
}
}
Then when you call it, you can do something like:
getUser { json, error in
guard let json = json else {
print(error)
return
}
// do something with json
print(json)
}
And just put your breakpoint in getUser's completion handler. And remember that you have no assurances that the completion handler will run on the main queue or not, so you'll want to make sure to dispatch and UI or model updates back to the main queue.

Related

Swift : HttpRequest works on Simulator but not on Device (iPhone 11)

I have an Authentication Manager that send SignIn and SignUp request. Code works good on simulator and fetch data from database but not working on device. On device It gives http 500 error. I check the textfields before sending the request and they seem fine.
Any suggestions to solve this issue ?
func performSignIn(email: String, password: String){
//1. Create URL
if let url = URL(string: Constants.signInURL){
var request = URLRequest(url: url)
//Request Body
var body = [String : Any]()
body["user"] = ["email": email,
"password": password]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
} catch {
print("JSON serialization failed: \(error)")
}
// Change the URLRequest to a POST request
request.httpMethod = "POST"
//Need to tell that request has json in body.
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
//2. Create URLSession
let session = URLSession(configuration: .default)
//3. Give Session a task
let task = session.dataTask(with: request) { (data, response, error) in
if error != nil{
DispatchQueue.main.async {
delegate?.failedWithError(error: error!)
return
}
}
if let safeData = data{
if let signInResponse = parseJSON(safeData){
//Who has the delegate run signInSuccess method
delegate?.signInSuccess(signInResponse)
}
}
}
//4. Start Task
task.resume()
}
}

How to call Async task in Swift?

I'm new to Swift and hence the question, I'm trying to wrap http calls into a function to reuse, however since it takes a completion block, I'm not sure how to call it. Here's my code,
func httpPost(_ path: String, _ parameters: [String: Any], completion:#escaping(_ ret:Any?,_ err:Error?) -> Void){
let headers = [
"Content-Type": "application/json",
"cache-control": "no-cache"
]
let postData = try? JSONSerialization.data(withJSONObject: parameters, options: [])
var request = URLRequest(url: URL(string: path)! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData
let session = URLSession.shared
let dataTask = session.dataTask(with: request) { (data, response, error) -> Void in
guard let data = data else {
completion(nil,error)
return
}
do{
try self.validate(response)
let json = try JSONSerialization.jsonObject(with: data, options: [])
completion(json,nil)
}catch{
print(error)
completion(nil,error)
}
}
dataTask.resume()
}
My question is how do I call this function?
I would assume that the Xcode itself would suggest how calling this method should be implemented. If you tried to type "httpPo...", you should see the autocompletion list:
double click on it:
I would assume that the path and parameters are easy to understand, it issue could be related to the completion closure; What you could do is to double click on it, therefore:
And that's pretty much it! All you have to do for now is to fill it:
httpPost("your path", [: ]) { (response, error) in
// ...
}

Datatask with semaphore not working as intended

I'm trying to make a URL request but waiting for it to be done prior to letting another URL request go through by using semaphore. Seems like the following code waits for semaphore timeout (10 seconds) and only then the datatask response happens.
In other words, execution does not go to semaphore.signal() before timeout happens. If I set the timeout to "distantFuture", the execution hangs.
static func makeHTTPPostRequestJsonWait(path: String, body: [String: AnyObject], onCompletion: #escaping ServiceResponse) {
var request = URLRequest(url: URL(string: path)!)
let semaphore = DispatchSemaphore(value: 0)
// Set the method to POST
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let json = JSON(body)
let jsonString = json.rawString()
// Set the POST body for the request
let jsonBody = jsonString?.data(using: String.Encoding.utf8)
request.httpBody = jsonBody
let session = URLSession.shared
let task = session.dataTask(with: request) { data, response, error in
semaphore.signal()
if let jsonData = data {
let json:JSON = JSON(data: jsonData)
onCompletion(json, nil)
} else {
onCompletion(nil, error)
}
}
task.resume()
_ = semaphore.wait(timeout: DispatchTime.now() + 10)
}

NSURLConnection sendSynchronousReques replacement

I have this chunk of code written in obj-c that I am trying to translate in Swift 3 but I encountered NSURLConnection.sendSynchronousRequest which is both deprecated and for my knowledge bad since it is using a Synchronous operation.
Here is the code :
NSData *responseData = [NSURLConnection sendSynchronousRequest:requestData returningResponse:&response error:&requestError];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
Do you have any suggestion in how I may re-write this in a better way and why so?
This is the minimum code you would need to make an async request, if you are replacing a lot of calls you should make an API layer and reuse code rather than copy/pasta everywhere.
let url = URL(string: "http://myURL.com")!;
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
let dictionary = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments)
}
task.resume()
URLSession is a replacement for NSURLConnection introduced in iOS 7. The URLSession class and related classes provide an API for downloading content via HTTP/HTTPS protocols. URLSession API is asynchronous by default.
Below is the simple Get request using URLSession API
public func simpleNetworkRequest(completion: #escaping (_ JSON: [[String: Any]]?, _ error: Error?) -> Void) {
// Set up the URL request
let todoUrl: String = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: todoUrl) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) { (data, response, error) in
guard error != nil else {
print(error!.localizedDescription)
completion(nil, error)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
completion(nil, nil)
return
}
// parse the result as JSON, since that's what the API provides
do {
guard let JSON = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: AnyObject] else {
print("error trying to convert data to JSON")
completion(nil, nil)
return
}
print("JSON response : \(JSON)")
//code to parse json response and send it back via completion handler
let result = JSON["result"] as? [[String: Any]]
completion(result, nil)
} catch let error {
print(error.localizedDescription)
completion(nil, error)
}
}
task.resume()
}
Below are free resources to get you stated
https://videos.raywenderlich.com/courses/networking-with-nsurlsession/lessons/1
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Alternatively you can use Alamofore (recommended) to make network requests
Simple example to make request would be
Alamofire.request("https://httpbin.org/get").responseJSON { response in
print(response.request) // original URL request
print(response.response) // HTTP URL response
print(response.data) // server data
print(response.result) // result of response serialization
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}
try like this
var request = NSMutableURLRequest(URL: NSURL(string: "request url")!)
var session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
var params = ["username":"username", "password":"password"] as Dictionary<String, String>
request.HTTPBody = try? NSJSONSerialization.dataWithJSONObject(params, options: [])
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
print("Response: \(response)")})
task.resume()

Controlling tasks within Swift 2.0

Working in Swift 2.0, on IOS 9.2.1 using Xcode 7.2
Learning Swift 2.0 and I have written a routine that creates a NSURL Session, gets back some JSON data and then parses it. It works great...
BUT I some help in understanding how make this work as in get the outer function, share_list_folders to wait until the task here truly completes so I can return the result?
var parsedJson:[String:AnyObject] = [:]
func shared_list_folders() {
// **** list_folders (shared) ****
let request = NSMutableURLRequest(URL: NSURL(string: "https://api.dropboxapi.com/2/sharing/list_folders")!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
request.addValue("Bearer ab-XXX", forHTTPHeaderField: "Authorization")
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
//print("Response: \(response)")
let strData = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Body: \(strData)\n\n")
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers);
self.jsonParser(jsonResult)
for (key, value) in self.parsedJson {
print("key2 \(key) value2 \(value)")
}
} catch {
print("Bang")
}
})
task.resume()
let string2return = parsedJson["path_lower"] as? String
return(string2return)!
}
Its not really a completion, cause task will go off and do its own thing and share_list_folders will complete? Obviously I don't get the path_lower value here, until its too late... looked at delegates? And I tried, but then I run into issued with the completion block...
As you noticed NSURLSession after resuming it's task go away and do it's job which requires time. It's part of asynchronous programming when you have to deal with situation when something is calculated/prepared in another thread. Under the hood NSURLSession has own thread and there is waiting for server response. Then invokes completionHandler on main thread. That's a simplified theory. Going back to your question:
It's obviously that you have to wait for server response. As in another languages code is executed line by line, so you can't return anything as you wrote. The answer is: use closures.
Function you declare could use delegate as well but let's focus on closures:
func shared_list_folders(completion: (string: String?, error: ErrorType?) -> Void) {
// **** list_folders (shared) ****
let request = NSMutableURLRequest(URL: NSURL(string: "https://api.dropboxapi.com/2/sharing/list_folders")!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
request.addValue("Bearer ab-XXX", forHTTPHeaderField: "Authorization")
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
//print("Response: \(response)")
if let error = error {
completion(string: nil, error: error)
return
}
let strData = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Body: \(strData)\n\n")
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers);
let parsedJson = self.jsonParser(jsonResult) // I assume this returns parsedJson?
let string2return = parsedJson["path_lower"] as? String
completion(string: string2return, error: nil)
} catch { // here we have an error
completion(string: nil, error: error)
}
})
task.resume()
}

Resources