How to call Async task in Swift? - ios

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
// ...
}

Related

URLRequest in Swift5

I'm using the OpenWeather Current Weather Data Api, and trying to make a url request to get json data from the api in Swift5. I need to print the json data. Here is some code I found on the internet that I have been trying to use, but has not been working.
Note: I do NOT want to use any external libraries. like alamofire.
let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid={APIKEY}")!
var request = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: request) { (data, response, error) in
if let error = error {
print(error)
} else if let data = data {
print(data)
} else {
print("nope")
}
}
task.resume()
The Openweathermap API documentation is a bit misleading, the expression {API key} indicates the API key without the braces.
Insert the key with String Interpolation
let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=\(APIKEY)")!
The URLRequest is not needed and dataTask returns either valid data or an error
let task = URLSession.shared.dataTask(with: url) { (data, _, error) in
if let error = error { print(error); return }
print(String(data: data!, encoding: .utf8)!)
}
task.resume()
To display the data create an appropriate model and decode the data with JSONDecoder
So, at first you should be aware that you are registered and already have your own API Key. The main reason that can occur here for not opening link is that You are using a Free subscription and try requesting data available in other subscriptions . And for future if you want to do just get request you don't need to do session.dataTask(with: request), the session.dataTask(with: url) will be OK.)
Here is simpler way of your code.
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid={APIKEY}") else {return}
let session = URLSession.shared
let task = session.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error)
} else if let data = data {
print(data)
} else {
print("nope")
}
}
task.resume()
Not all APIs work with just URL
So if the API has a header in request, try this code.
Note: The header are dependent on your API.
let semaphore = DispatchSemaphore (value: 0)
let param = [
"language": "english",
"serviceRequestId": 1,
"location": ["latitude": "12.34","longitude": "12.34"]
] as [String : Any]
var request = URLRequest(url: URL(string: "UrlHere")!,timeoutInterval: Double.infinity)
request.addValue("tokenHere", forHTTPHeaderField: "Authorization")
do{
let i = try JSONSerialization.data(withJSONObject: param, options: .prettyPrinted)
// print("\(type(of: i))")
print(String(data: i,
encoding: .ascii) ?? "nil")
request.httpMethod = "POST"
request.httpBody = i
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
semaphore.signal()
do{
let postData = try JSONDecoder().decode(ModelRootClassHere.self, from: data)
print(postData)
MyData = postData
completion()
}
catch{
print(error)
print("error............")
}
}
task.resume()
semaphore.wait()
}catch{
print(error)
}

Retrieving json data from http request in swift

I'm new to swift and thus the question. I want to wrap my http calls into a function of this signature.
func httpPost()-> Any
This code works but how can I wrap this code in the functio signature I want.
let headers = [
"Content-Type": "application/json",
"cache-control": "no-cache"
]
let parameters = [
"client_id": "xxx",
"client_secret": "yyy"
] as [String : Any]
let postData = try? JSONSerialization.data(withJSONObject: parameters, options: [])
var request = URLRequest(url: URL(string: "http://xxx.xxx")! 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 {return}
do{
try validate(response)
let json = try JSONSerialization.jsonObject(with: data, options: [])
}catch{
print(error)
}
//print(String(describing: data))
}
dataTask.resume()
I want to return the json object as Any here
You can't return a direct value in an asynchronous function until you block the thread which is a bad idea , so you want a completion
func httpPost(completion:#escaping(_ ret:Any?,err:Error?) -> Void)
let headers = [
"Content-Type": "application/json",
"cache-control": "no-cache"
]
let parameters = [
"client_id": "xxx",
"client_secret": "yyy"
] as [String : Any]
let postData = try? JSONSerialization.data(withJSONObject: parameters, options: [])
var request = URLRequest(url: URL(string: "http://xxx.xxx")! 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 validate(response)
let json = try JSONSerialization.jsonObject(with: data, options: [])
completion(json,nil)
}catch{
print(error)
completion(nil,error)
}
//print(String(describing: data))
}
dataTask.resume()
}
To call
httpPost { (json,error) in
print(json)
}
also it's better to cast the json as [Any] / [String:Any] for Array/ Dictionary response respectively

Feedback json format wrong

This is my feedback json string:
{"name":"abc", "cardNumber":"1234567890", "data": [{A data},{B data}...]}
I use this function to send data, then get json and encode:
func uploadData(word:String){
var request = URLRequest(url: url!, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30)
request.httpMethod = "POST"
request.httpBody = word.data(using: .utf8)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let session = URLSession(configuration: URLSessionConfiguration.default)
session.dataTask(with: request, completionHandler: {(data, response, error) in
if let data = data{
do{
let data = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers)
print(data) //I want to know what is this so I print
}catch{
print(error.localizedDescription)
}
}
}).resume()
}
But the console always says:The data couldn't be read because it isn't in the correct format
This json can be format and read in android if I use JSONObject.getJSONArray("myValue")...
I try to use print(data)(without json encode) to show if there is any data in feedback and I get 400byte in console, so I'm sure there is data send back to me.
UPDATE 12/28:
{"name":"abc",
"cardNumber":"1234567890",
"data": [{day:20171228, time: 09:10:11},
{day:20171226, time: 20:00:12},
{day:20171227, time: 15:30:22}
]
}
I'm sure this json can be read in android, the receiver and sender I use is vb.net, it use sendingString = JsonConvert.SerializeObject(JSONClass) to become json string, then convert to byte to send out.
UPDATE 12/28 new
After trying so much, I found string can get the feedback, but the value of name is Chinese word, other value is English and number, only name is unreadable, now I'm checking which String.Encoding will work, then if I encode it success, I will try to format to json Array.
Can you try this one?
func uploadData(word:String){
var request = URLRequest(url: url!, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30)
request.httpMethod = "POST"
request.httpBody = word.data(using: .utf8)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let session = URLSession(configuration: URLSessionConfiguration.default)
session.dataTask(with: request, completionHandler: {(data, response, error) in
if let data = data{
if let returnData = String(data: data!, encoding: .utf8) {
print(returnData)
} else {
print("Invalid Data Coming")
}
do{
let data = try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject]
print(data) //I want to know what is this so I print
}catch{
print(error.localizedDescription)
}
}
}).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.

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