Process a dataTask GET request on background - ios

I have an API to get about 1000 records from a GET request. Currently I'm using dataTask to get the content via URLSession. Complication is the request just freeze as soon as it enters to the background mode. When i did some research I found out that NSURLSessionDataTask doest support the background mode and instead I have to use downloadTask with backgroundSessionConfiguration. Hence, I have troubles with modifying my code accordingly. My current code as bellow.
class Network: NSObject {
//Singleton
public static var shared: Network {
if networkCalls == nil {
networkCalls = Network()
}
return networkCalls!
}
//Place where issue is converting the dataTask to downloadTask
private func performWebServiceRequest <T: Codable>(type: T.Type, with url: URL, contentType: CONTENT_TYPE? = nil, requestType: String, paramData: T? = nil, requestOptions: [String: String]?, responseStructure: String, successBlock: #escaping SuccessBlock, failureBlock: #escaping FailureBlock) {
let session = URLSession.shared
let request = NSMutableURLRequest(url: url)
request.httpMethod = requestType // As in "POST", "GET", "PUT" or "DELETE"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
guard let data: Data = data, let response: URLResponse = response, error == nil else
{
failureBlock(0, ERROR_MESSAGE)
}
let responseStatusCode: Int = (response as! HTTPURLResponse).statusCode
if reponseStatusCode == 200 {
do {
let result = try JSONDecoder().decode(type, from: data)
successBlock(result)
} catch {
failureBlock(0,"Error")
}
}
}
task.resume()
}
}

Related

the code is not entering the completion handler of the data task

The completion handler code is not getting executed. while i debug the code its coming till the session.datatask and after that its not getting into the completion handler. we are actually migrating this project from objectivec to swift. i am not sure if the request generated is hitting the django or not. how to debug further. after the datatask the control exits the if request loop and there is smooth execution without any error. but the data is not fetched from DB.
func makeRequest(_ url: String?, path: String?, httpMethod: String?, httpBody httpBoday: Data?, completion: #escaping (_ result: [AnyHashable : Any]?, _ error: Error?) -> Void) {
let headers = [
"cache-control": "no-cache",
"Authorization": "Token f491fbe3ec54034d51e141e28aaee87d47bb7e74"
]
var request: URLRequest? = nil
if let url = URL(string: "\(url ?? "")\(path ?? "")") {
request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
}
request?.httpMethod = httpMethod ?? ""
request?.allHTTPHeaderFields = headers
let configuration = URLSessionConfiguration.default
configuration.httpCookieStorage = nil
configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData
if #available(iOS 11.0, *) {
configuration.waitsForConnectivity = false
}
let session = URLSession(configuration: configuration)
// let session = URLSession.shared
var task: URLSessionDataTask? = nil
print ("Request =======>",request)
if let request = request {
task = session.dataTask(with: request , completionHandler: { data, response, error in
//.dataTask(with: request, completionHandler: { data, response, error in
print ("testing******************")
var result: Any? = nil
if error != nil {
if let error = error {
print("\(error)")
}
if completion != nil {
completion(nil, error)
}
} else
{
var string: String? = nil
if let data = data {
string = String(data: data, encoding: .utf8)
}
string = self.string(byRemovingControlCharacters: string)
do {
if let data = string?.data(using: .utf8) {
result = try JSONSerialization.jsonObject(with: data, options: []) as! [AnyHashable : Any]
print ("Result ===============>",result)
}
} catch {
print("Error while getting the data from json object")
}
DispatchQueue.main.async() {
if completion != nil {
completion(result as! [AnyHashable : Any], error)
}
}
}
})
}
else
{
print("Inside the else block of request")
}
task?.resume()
}
This line is your problem:
if let url = URL(string: "\(url ?? "")\(path ?? "")") {
request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
}
It needs to be like this:
guard let urlString = url else { return }
guard let path = path else { return }
if let requestURL = URL(string: "\(urlString)\(path)" {
request = URLRequest(url: requestURL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
}
Also, shouldn't make a difference, but you have 2 different cachePolicies.
Also, you're not using the httpBody anywhere. I suppose your request doesn't need it, but thought I would remind you to take a look.

Synchronous API request to Asynchronous API request Swift 2.2

Well I am new to Swift and I don't know much of completion handler. I want to get a request from an API and parse the JSON response so I can get the token. But what's happening with my code is that whenever I call the getAuthentication function my UI freezes and waiting for the data to get. Here is the code for getAuthentication
func getAuthentication(username: String, password: String){
let semaphore = dispatch_semaphore_create(0);
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
//parse the data to get the user
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
}
dispatch_semaphore_signal(semaphore);
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
then, I am calling this method in my LoginViewController. Someone says that I am using a Synchronous request thats why my UI freezes, but I have really no idea on how to change it to Async and wait for the data to be downloaded. Can someone help me with this? Any help will much be appreciated.
Firstly, remove dispatch_semaphore related code from your function.
func getAuthentication(username: String, password: String){
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
//parse the data to get the user
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
}
}
task.resume()
}
In the above code, the function dataTaskWithRequest itself is an asynchronus function. So, you don't need to call the function getAuthentication in a background thread.
For adding the completion handler,
func getAuthentication(username: String, password: String, completion:((sucess: Bool) -> Void)){
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
var successVal: Bool = true
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
successVal = false
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(successVal)
})
}
task.resume()
}
It can be called as follows:
self.getAuthentication("user", password: "password", completion: {(success) -> Void in
})
You may pass an escaping closure argument to getAuthentication method.
func getAuthentication(username: String, password: String, completion: (JSON) -> ()){
...
// create a request in the same way
...
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
completion(swiftyJSON)
} else {
print("There was an error")
}
}
task.resume()
}
And call getAuthentication in LoginViewController like this:
getAuthentication(username, password) { (json) -> in
//Do whatever you want with the json result
dispatch_async(dispatch_get_main_queue()) {
// Do UI updates
}
}
Another way to go is calling getAuthentication in a background thread in your LoginViewController to avoid blocking the main thread (i.e. UI thread).
//In LoginViewController
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
getAuthentication(username, password)
dispatch_async(dispatch_get_main_queue()) {
// UI updates
}
}

How to use httpMethod = POST on Swift 3 , URLRequest?

I have working json fetching codes with GET methods but when i convert it to POST with adding;
request.httpMethod = "POST"
gives me ;
Cannot assign to property: 'httpMethod' is a get-only property Error
My codes under below how can fix it ? How can i convert it to POST
#discardableResult
open func getLoginMethod(_ method: String, parameters: String, completionHandler: #escaping (_ login: [Login]?, _ error: Error?) -> Void) -> URLSessionTask! {
let session = URLSession.shared
guard let url = NSURL(string: parameters) else {
completionHandler(nil, myErrors.InvalidUrlError)
return nil
}
let request = NSURLRequest(url: url as URL)
let task = session.dataTask(with: request as URLRequest) { data, response, error in
if error != nil {
completionHandler(nil, myErrors.getError)
} else {
do {
let login = try self.getLogin(jsonData: data! as NSData)
completionHandler(login, nil)
} catch {
completionHandler(nil, error)
}
}
}
task.resume()
return task
}
Also my calling codes under below for get method my variables inside parameters , for POST method which changes i need to do ?
AWLoader.show(blurStyle: .dark, shape: .Circle)
DispatchQueue.global(qos: .background).async {
myApp.sharedInstance().getLoginMethod("", parameters: "http://bla.com/Post?Phone=\(self.phone.text)") {login, error in
if error != nil {
DispatchQueue.main.async {
// self.showAlert()
print(error!)
AWLoader.hide()
}
} else {
DispatchQueue.main.async {
self.getlogin = login!
// self.collectionView.reloadData()
// AWLoader.hide()
// self.loginButton.alpha = 0
print(self.getlogin)
AWLoader.hide()
}
}
}
}
Thanks
You need to use NSMutableURLRequest to be able to modify httpMethod. Using NSURLRequest won't do.
Example:
let request = NSMutableURLRequest(url: url)
request.httpMethod = "POST"
But even better, you should be using URLRequest struct:
var request = URLRequest(url: url)
request.httpMethod = "POST"
Your refactored code with additional improvements:
#discardableResult
open func getLoginMethod(_ method: String, parameters: String,
session: URLSession = .shared,
completionHandler: #escaping (_ login: [Login]?, _ error: Error?) -> Void) -> URLSessionTask! {
guard let url = URL(string: parameters) else {
completionHandler(nil, myErrors.InvalidUrlError)
return nil
}
var request = URLRequest(url: url)
request.httpMethod = method
let task = session.dataTask(with: request) { data, _, _ in
guard let responseData = data else {
completionHandler(nil, myErrors.getError)
return
}
do {
let login = try self.getLogin(jsonData: data as NSData)
completionHandler(login, nil)
} catch {
completionHandler(nil, error)
}
}
task.resume()
return task
}
EDIT:
To answer your second part of the question. It depends on what kind of data format your API is expecting - whether it should be multipart/form-data or json format, it all depends.
But in general, assuming that you are using URLRequest struct that I've mentioned, you should just convert it to Data using your serializer and use it like this:
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = <your parameters converted to Data>
Usually, your API will expect proper Content-Type header added to your URLRequest:
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

how do i get ios swift json return function value in another view controller

func data_request(){
let url:NSURL = NSURL(string: url_to_request)!
let session = URLSession.shared
let request = NSMutableURLRequest(url: url as URL)
//request.addValue("application/json", forHTTPHeaderField: "Content-Type")
//request.addValue("application/json", forHTTPHeaderField: "Accept")
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
//let paramString = "data=Hello"
let paramStrings = paramString
request.httpBody = paramStrings.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request as URLRequest) { ( data, response, error) in
guard let :NSData = data as NSData?, let :URLResponse = response , error == nil else {
// print("error") //
return //
}
let dataString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print(dataString)
};
task.resume()
}
how do i get the return value datastring in another view ?? how do i pass this value..iam a beginner in ios..plshelp ....thanks in advance
Using block you can do this
For Swift. Use AnyObject for id objc type.
func callWebservice (serviceName: String, withMethod method: String, andParams params: NSDictionary, showLoader loader: Bool, completionBlockSuccess aBlock: ((AnyObject) -> Void), andFailureBlock failBlock: ((AnyObject) -> Void)) {
if loader {
// Show loader
}
performRequestWithServiceName(serviceName, method: method, andParams: params, success: aBlock, failure: failBlock)
}
func performRequestWithServiceName(serviceName: String, method methodName: String, andParams params: NSDictionary, success successBlock: ((AnyObject) -> Void), failure failureBlock: ((AnyObject) -> Void)) {
if callSuceess {
successBlock("Success")
}else {
successBlock(nil)
}
}
An example when you want call web service. See code below
callWebservice("your-service-name", withMethod: "your-method", andParams: ["your-dic-key": "your dict value"], showLoader: true/*or false*/, completionBlockSuccess: { (success) -> Void in
// your successful handle
}) { (failure) -> Void in
// your failure handle
}
from :https://stackoverflow.com/a/31491077/3901620

Swift 3 getURL Ambiguous Data [duplicate]

Hello I have working json parsing code for swift2.2 but when i use it for Swift 3.0 gives me that error
ViewController.swift:132:31: Ambiguous reference to member 'dataTask(with:completionHandler:)'
My code is here:
let listUrlString = "http://bla.com?batchSize=" + String(batchSize) + "&fromIndex=" + String(fromIndex)
let myUrl = URL(string: listUrlString);
let request = NSMutableURLRequest(url:myUrl!);
request.httpMethod = "GET";
let task = URLSession.shared().dataTask(with: request) {
data, response, error in
if error != nil {
print(error!.localizedDescription)
DispatchQueue.main.sync(execute: {
AWLoader.hide()
})
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSArray
if let parseJSON = json {
var items = self.categoryList
items.append(contentsOf: parseJSON as! [String])
if self.fromIndex < items.count {
self.categoryList = items
self.fromIndex = items.count
DispatchQueue.main.async(execute: {
self.categoriesTableView.reloadData()
AWLoader.hide()
})
}else if( self.fromIndex == items.count){
DispatchQueue.main.async(execute: {
AWLoader.hide()
})
}
}
} catch {
AWLoader.hide()
print(error)
}
}
task.resume()
Thanks for ideas.
The compiler is confused by the function signature. You can fix it like this:
let task = URLSession.shared.dataTask(with: request as URLRequest) {
But, note that we don't have to cast "request" as URLRequest in this signature if it was declared earlier as URLRequest instead of NSMutableURLRequest:
var request = URLRequest(url:myUrl!)
This is the automatic casting between NSMutableURLRequest and the new URLRequest that is failing and which forced us to do this casting here.
You have init'd myRequest as NSMutableURLRequest, you need this:
var URLRequest
Swift is ditching both the NSMutable... thing. Just use var for the new classes.
Xcode 8 and Swift 3.0
Using URLSession:
let url = URL(string:"Download URL")!
let req = NSMutableURLRequest(url:url)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
let task : URLSessionDownloadTask = session.downloadTask(with: req as URLRequest)
task.resume()
URLSession Delegate call:
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64, totalBytesWritten writ: Int64, totalBytesExpectedToWrite exp: Int64) {
print("downloaded \(100*writ/exp)" as AnyObject)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL){
}
Using Block GET/POST/PUT/DELETE:
let request = NSMutableURLRequest(url: URL(string: "Your API URL here" ,param: param))!,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval:"Your request timeout time in Seconds")
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers as? [String : String]
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest) {data,response,error in
let httpResponse = response as? HTTPURLResponse
if (error != nil) {
print(error)
} else {
print(httpResponse)
}
DispatchQueue.main.async {
//Update your UI here
}
}
dataTask.resume()
Working fine for me.. try it 100% result guarantee
This problem is caused by URLSession has two dataTask methods
open func dataTask(with request: URLRequest, completionHandler: #escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> URLSessionDataTask
open func dataTask(with url: URL, completionHandler: #escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> URLSessionDataTask
The first one has URLRequest as parameter, and the second one has URL as parameter, so we need to specify which type to call, for example, I want to call the second method
let task = URLSession.shared.dataTask(with: url! as URL) {
data, response, error in
// Handler
}
In my case error was in NSURL
let url = NSURL(string: urlString)
In Swift 3 you must write just URL:
let url = URL(string: urlString)
Tested xcode 8 stable version ; Need to use var request variable with URLRequest() With thats you can easily fix that (bug)
var request = URLRequest(url:myUrl!) And
let task = URLSession.shared().dataTask(with: request as URLRequest) { }
Worked fine ! Thank you guys, i think help many people. !
For Swift 3 and Xcode 8:
var dataTask: URLSessionDataTask?
if let url = URL(string: urlString) {
self.dataTask = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error {
print(error.localizedDescription)
} else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
// You can use data received.
self.process(data: data as Data?)
}
})
}
}
//Note: You can always use debugger to check error
In swift 3 the compiler is confused by the function signature. Specifying it will clear the error. Also convert the url string to type URL. The following code worked for me.
let urlString = "http://bla.com?batchSize="
let pathURL = URL(string: urlString)!
var urlRequest = URLRequest(url:pathURL)
let session = URLSession.shared
let dataTask = session.dataTask(with: urlRequest as URLRequest) { (data,response,error) in
Short and concise answer for Swift 3:
guard let requestUrl = URL(string: yourURL) else { return }
let request = URLRequest(url:requestUrl)
URLSession.shared.dataTask(with: request) {
(data, response, error) in
...
}.resume()
// prepare json data
let mapDict = [ "1":"First", "2":"Second"]
let json = [ "title":"ABC" , "dict": mapDict ] as [String : Any]
let jsonData : NSData = NSKeyedArchiver.archivedData(withRootObject: json) as NSData
// create post request
let url = NSURL(string: "http://httpbin.org/post")!
let request = NSMutableURLRequest(url: url as URL)
request.httpMethod = "POST"
// insert json data to the request
request.httpBody = jsonData as Data
let task = URLSession.shared.dataTask(with: request as URLRequest){ data,response,error in
if error != nil{
return
}
do {
let result = try JSONSerialization.jsonObject(with: data!, options: []) as? [String:AnyObject]
print("Result",result!)
} catch {
print("Error -> \(error)")
}
}
task.resume()
To load data via a GET request you don't need any URLRequest (and no semicolons)
let listUrlString = "http://bla.com?batchSize=" + String(batchSize) + "&fromIndex=" + String(fromIndex)
let myUrl = URL(string: listUrlString)!
let task = URLSession.shared.dataTask(with: myUrl) { ...
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { data,response,error in
if error != nil{
print(error!.localizedDescription)
return
}
if let responseJSON = (try? JSONSerialization.jsonObject(with: data!, options: [])) as? [String:AnyObject]{
if let response_token:String = responseJSON["token"] as? String {
print("Singleton Firebase Token : \(response_token)")
completion(response_token)
}
}
})
task.resume()
Xcode 10.1 Swift 4
This worked for me:
let task: URLSessionDataTask = session.dataTask(with: request as URLRequest) { (data, response, error) -> Void in
...
The key was adding in the URLSessionDataTask type declaration.
For me I do this to find,
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { (data, response, error) in ...}
Can't use
"let url = NSURL(string: urlString)

Resources