Controlling tasks within Swift 2.0 - ios

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()
}

Related

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()

Swift 3: URLSession / URLRequest Not Working

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.

Where do I specify ReloadIgnoringLocalCacheData for NSURLSession in Swift 2

I have an app that makes an API call every 5 seconds using NSURLSession and p2-oauth2. I'm running into an issue of it returning cached data instead of the updated information from the API. I read this post by Matt Thompson where he describes the different cache policies, the one I think I need to use is ReloadIgnoringLocalCacheData. I think it's suppose to be put in the AppDelegate DidFinishLaunchingWithOptions functions. But, the problem I'm having is I don't know where or how to specify it. I haven't found any Swift solutions. Can anyone tell me what my function should say?
If it's helpful, here is my API request:
let urlPath = "https://sandbox-api.uber.com/v1/requests/\(uberRequestId)"
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
guard let endpoint = NSURL(string: urlPath) else { print("Error creating endpoint");return }
let request = appDelegate.oauth.request(forURL: NSURL(string:urlPath)!)
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.HTTPMethod = "GET"
//get response from Uber and iterate through to find Uber Product ID.
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
guard let dat = data else { throw JSONError.NoData }
let result = try NSJSONSerialization.JSONObjectWithData(dat, options: NSJSONReadingOptions.MutableContainers)
print(result)
//set status
status = result["status"] as! String
print("found status...returning it back -> \(status)")
completion(status: "\(status)")
} catch let error as JSONError {
print(error.rawValue)
print("ERROR NEEDS TO BE HANDLED.")
} catch {
print(error)
print("ERROR NEEDS TO BE HANDLED.")
}
}.resume()
Here is the final request that properly sets the cache policy. I added one line with ReloadIgnoringLocalCacheData.
let urlPath = "https://sandbox-api.uber.com/v1/requests/\(uberRequestId)"
let url:NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let request = appDelegate.oauth.request(forURL: NSURL(string:urlPath)!)
request.HTTPMethod = "GET"
//added this line to set cache policy
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
let task = session.dataTaskWithRequest(request) {
(
let data, let response, let error) in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(dataString)
}
task.resume()
Assuming the OAuth API returns a mutable request, you can set its cachePolicy property to NSURLRequestCachePolicy.ReloadIgnoringCacheData.

Create and send json data to server using swift language and iOS 9+

I really need a code for send and receive data from server with JSON, i find a really good code but it isn't compatible with iOS9.
#IBAction func submitAction(sender: AnyObject) {
//declare parameter as a dictionary which contains string as key and value combination.
var parameters = ["name": nametextField.text, "password": passwordTextField.text] as Dictionary<String, String>
//create the url with NSURL
let url = NSURL(string: "http://myServerName.com/api") //change the url
//create the session object
var session = NSURLSession.sharedSession()
//now create the NSMutableRequest object using the url object
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST" //set http method as POST
var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(parameters, options: nil, error: &err) // pass dictionary to nsdata object and set it as request body
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
println("Response: \(response)")
var strData = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Body: \(strData)")
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves, error: &err) as? NSDictionary
// Did the JSONObjectWithData constructor return an error? If so, log the error to the console
if(err != nil) {
println(err!.localizedDescription)
let jsonStr = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Error could not parse JSON: '\(jsonStr)'")
}
else {
// The JSONObjectWithData constructor didn't return an error. But, we should still
// check and make sure that json has a value using optional binding.
if let parseJSON = json {
// Okay, the parsedJSON is here, let's get the value for 'success' out of it
var success = parseJSON["success"] as? Int
println("Succes: \(success)")
}
else {
// Woa, okay the json object was nil, something went worng. Maybe the server isn't running?
let jsonStr = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Error could not parse JSON: \(jsonStr)")
}
}
})
task.resume() }
Really thanks for the help
Swift syntax changed a little bit, but not significantly to break the whole code.
You will need to adjust few things like
println(err!.localizedDescription)
to
print(err!.localizedDescription)
Then your code will compile
Maybe have a look into the Alamofire Framework.
It really is making your life easier when it comes to handling HTTP requests.
Otherwise, as vadian suggested, check out the Swift 2 (do-try-catch) Errorhandling.
I have found a great tutorial Project from deege.
https://github.com/deege/deegeu-swift-rest-example
Here a breakdown of a HTTP request.
// Setup the session to make REST GET call. Notice the URL is https NOT http!! (if you need further assistance on how and why, let me know)
let endpoint: String = "https://yourAPI-Endpoint"
let session = NSURLSession.sharedSession()
let url = NSURL(string: endpoint)!
// Make the call and handle it in a completion handler
session.dataTaskWithURL(url, completionHandler: { ( data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
// Make sure we get an OK response
guard let realResponse = response as? NSHTTPURLResponse where
realResponse.statusCode == 200 else {
print("Not a 200 response")
return
}
// Read the JSON
do {
if let jsonString = NSString(data:data!, encoding: NSUTF8StringEncoding) {
// Print what we got from the call
print(jsonString)
// Parse the JSON
let jsonDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
let value = jsonDictionary["key"] as! String
}
} catch {
print("bad things happened")
}
}).resume()

NSURLConnection sendAsynchronousRequest can't get variable out of closure

I'm trying to get a simple text response from a PHP page using POST. I have the following code:
func post(url: String, info: String) -> String {
var URL: NSURL = NSURL(string: url)!
var request:NSMutableURLRequest = NSMutableURLRequest(URL:URL)
var output = "Nothing Returned";
request.HTTPMethod = "POST";
var bodyData = info;
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){
response, data, error in
output = (NSString(data: data, encoding: NSUTF8StringEncoding))!
}
return output
}
While this code does not throw any errors, when I make a call to it like this:
println(post(url, info: data))
It only prints: "Nothing Returned" even though if I were to change the line:
output = (NSString(data: data, encoding: NSUTF8StringEncoding))!
to this:
println((NSString(data: data, encoding: NSUTF8StringEncoding)))
it does print out the proper response. Am I doing something wrong with my variables here?
This is calling asynchronous function that is using a completion handler block/closure. So, you need to employ the completion handler pattern in your own code. This consists of changing the method return type to Void and adding a new completionHandler closure that will be called when the asynchronous call is done:
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "POST"
let bodyData = info
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { response, data, error in
guard data != nil else {
completionHandler(nil, error)
return
}
completionHandler(NSString(data: data!, encoding: NSUTF8StringEncoding), nil)
}
}
Or, since NSURLConnection is now formally deprecated, it might be better to use NSURLSession:
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) -> NSURLSessionTask {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "POST"
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 {
completionHandler(nil, error)
return
}
completionHandler(NSString(data: data!, encoding: NSUTF8StringEncoding), nil)
}
}
task.resume()
return task
}
And you call it like so:
post(url, info: info) { responseString, error in
guard responseString != nil else {
print(error)
return
}
// use responseString here
}
// but don't try to use response string here ... the above closure will be called
// asynchronously (i.e. later)
Note, to keep this simple, I've employed the trailing closure syntax (see Trailing Closure section of The Swift Programming Language: Closures), but hopefully it illustrates the idea: You cannot immediately return the result of an asynchronous method, so provide a completion handler closure that will be called when the asynchronous method is done.

Resources