I wanted to reduce repetition in this code which I got from ExDeveloper. I have a server model where I've all my networking code. Now I've just shown 3 methods here. But eventually it'll grow & there would be 70+ methods. I don't want to repeat task code which is common in all methods.
What is the best way to do it without using a 3rd party like Alamofire, etc.?
func login(userName: String, password: String, onSuccess: #escaping(JSON) -> Void, onFailure: #escaping(Error) -> Void){
let url : String = ""
let request: NSMutableURLRequest = NSMutableURLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "GET"
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
if(error != nil){
onFailure(error!)
} else{
print(response as Any)
}
})
task.resume()
}
//====================
func signUp(userName: String, password: String, email: String, onSuccess: #escaping(JSON) -> Void, onFailure: #escaping(Error) -> Void){
let url : String = ""
let request: NSMutableURLRequest = NSMutableURLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "GET"
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
if(error != nil){
onFailure(error!)
} else{
print(response as Any)
}
})
task.resume()
}
//====================
func forgot(email: String, onSuccess: #escaping(JSON) -> Void, onFailure: #escaping(Error) -> Void){
let url : String = ""
let request: NSMutableURLRequest = NSMutableURLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "GET"
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
if(error != nil){
onFailure(error!)
} else{
print(response as Any)
}
})
task.resume()
}
You could split up the requests into more modular components like network layers.
Check out this guide: https://medium.com/#danielemargutti/network-layers-in-swift-7fc5628ff789
As you've written the above code, each function is exactly identical. So put that in a separate function that they all call. If there are parts that aren't identical (that you didn't provide), then pass those to the shared function. If there is code that changes, pass that as a function parameter.
In short: extract the parts that don't change, and pass the parts that do as parameters.
Related
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()
}
}
I have a problem with my swift. I am trying to send an API request and then retrieve data but I get the following error message:
"Swift: Escaping closure captures non-escaping parameter 'onCompletion'".
Does anyone know how I can solve this? thanks in advance
Code:
class RestApiManager: NSObject {
static let sharedInstance = RestApiManager()
let baseURL = "http://api.randomuser.me/"
func getRandomUser(onCompletion : (JSON) -> Void) {
makeHTTPGetRequest(path: baseURL, onCompletion: { json, err -> Void in
onCompletion(json)
})
}
func makeHTTPGetRequest(path: String, onCompletion: ServiceResponse) {
let request = NSMutableURLRequest(url : URL(string: path)! as URL)
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
let json:JSON = JSON(data as Any)
onCompletion(json, error as NSError?)
})
task.resume()
}
}
You have to mark both completion handlers with #escaping. Usually the compiler offers a fix
class RestApiManager: NSObject {
static let sharedInstance = RestApiManager()
let baseURL = "http://api.randomuser.me/"
func getRandomUser(onCompletion : #escaping (JSON) -> Void) {
makeHTTPGetRequest(path: baseURL, onCompletion: { json, err -> Void in
onCompletion(json)
})
}
func makeHTTPGetRequest(path: String, onCompletion: #escaping ServiceResponse) {
let request = NSMutableURLRequest(url : URL(string: path)! as URL)
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
let json:JSON = JSON(data as Any)
onCompletion(json, error as NSError?)
})
task.resume()
}
}
Answers here were right on adding #escaping before the completion handler parameter declaration, albeit shortly explained.
Here's what I was missing for the full picture, taken from Swift's documentation:
Escaping Closures
A closure is said to escape a function when the
closure is passed as an argument to the function, but is called after
the function returns. When you declare a function that takes a closure
as one of its parameters, you can write #escaping before the
parameter’s type to indicate that the closure is allowed to escape.
So basically, if you want a completion handler to be called AFTER the method returns, it is defined as escaping in swift, and should be declared as such:
func makeHTTPGetRequest(path: String, onCompletion: #escaping ServiceResponse)
This is happning due to your parameter onCompletion. By default it is #nonesacping you have to marke it #esacping so it can be worked in completionHandler closure.
func makeHTTPGetRequest(path: String, onCompletion: #escaping ServiceResponse)
Use this:
class RestApiManager: NSObject {
static let sharedInstance = RestApiManager()
let baseURL = "http://api.randomuser.me/"
func getRandomUser(onCompletion : #escaping (JSON) -> Void) {
makeHTTPGetRequest(path: baseURL, onCompletion: { json, err -> Void in
onCompletion(json)
})
}
func makeHTTPGetRequest(path: String, onCompletion: #escaping ServiceResponse) {
let request = NSMutableURLRequest(url : URL(string: path)! as URL)
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
let json:JSON = JSON(data as Any)
onCompletion(json, error as NSError?)
})
task.resume()
}
}
I am trying to make dispatchgroup work in my code
let dispatchQueue:DispatchQueue = DispatchQueue(label: "com.dispatchgroup", attributes: .concurrent, target: .main)
var dispatchGroup:DispatchGroup = DispatchGroup()
func renewLoginIfRequired()->String{
self.dispatchGroup.enter()
dispatchQueue.async(group:dispatchGroup){
self.authorizeApplication(completionHandler: {
self.dispatchGroup.leave()
})
}
}
self.dispatchGroup.wait() // Stops execution here
return "Login Success"
}
Above code stops execution at self.dispatchGroup.wait().
I have tried the same code without dispatchQueue.async(group:dispatchGroup) around self.authorizeApplication as well with no luck
I am not sure what am i doing wrong. One thing to mention here is that self.authorizeApplication will make an async web service request within that function
Edit 1:
To elaborate the problem even more. I will be returning a session token (String) from this method.
Here is the function which is calling this
func processPOSTRequest(_ urlString : String, isAnAuthReq:Bool = false, requestObject: Data?, onSuccess: #escaping (NSDictionary?, NSError?) -> Void, onFailure: #escaping (Data?, NSError?) -> Void){
let manager = AFHTTPSessionManager()
let url = URL(string: urlString);
var request = URLRequest(url: url!);
if !isAnAuthReq{
let token = self.renewLoginIfRequired() //-- Get new token before processing a webservice request
request.setValue(token, forHTTPHeaderField: "Authorization")
}
print("processing URl : "+urlString)
request.httpMethod="POST";
request.httpBody = requestObject
request.setValue("application/json; charset=utf-8", forHTTPHeaderField:"Content-Type")
request.setValue("application/json; charset=utf-8", forHTTPHeaderField:"Accept")
let task = manager.dataTask(with: request, uploadProgress: nil, downloadProgress: nil, completionHandler:{ data, response, error in
if(error == nil){
if let responseCode = (data as? HTTPURLResponse)?.statusCode{
if responseCode != 200 || !((response as? [String: Any])?["success"] as? Bool)!{
let errorResponse = NSError()
print("Response received for URL: "+urlString)
onFailure(nil, errorResponse.addItemsToUserInfo(newUserInfo: ["errorCode":String(responseCode)]))
}
else{
onSuccess(response as! NSDictionary?, nil)
}
}
}
else{
onFailure(nil, error as NSError?)
}
})
task.resume()
}
If i use Notify or closure. How can i do that? I have tried both of them
I don't know what is under the hood of the authorizeApplication request, but it's likely that its callback performs in the main thread, that is, in the main queue. So you enter, leave and wait in the same queue, therefore you can't reach the self.dispatchGroup.leave() after you've invoked self.dispatchGroup.wait().
To handle that you need to redesign the async request and call self.dispatchGroup.leave() in a background queue.
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")
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)