May this question be dumb, but I was looking a way to create optional responses like Alamofire 4.0 have (eg. responseJSON,responseData, responseString etc). For example, in my project I have BaseService which make the request (using alamofire) then handle the response (for erros, if has, it calls a exception class which shows a message an break the flow). So, I have subclasses that inherit from my BaseService, and my methods has completions blocks who parse and pass any data (or error if need) from BaseService.
Theen, my question is: my BaseService request function may return (as block) a response, json or an error, ex: completionHandler(response,json, error) or completionHandler(nil, json, nil)
So when I don't need a response or json, just want verify if error isn't nil I've to do like this:
myFunc() { ( _ , _,error) in }
How do I do to get only the block that I want? Like Alamofire do with his response?
You can divide your completionHandler at you BaseService class to each service function to onSuccess and onFail ... etc
Example:
func logInUser( _ userEmail : String, userPassword : String, onSuccess: #escaping (Any?)-> Void, onFail : #escaping (Error?) ->(Void)) {
let url : URLConvertible = urls.loginUser
let parameters = ["email" : userEmail, "password" : userPassword]
let header = ["Authorization" : APPSECRETKEY ]
alamofireManager.request(url, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: header).responseJSON(completionHandler: { response in
if response.result.value != nil && response.result.error == nil {
onSuccess(response.result.value)
}
else
{
onFail(response.result.error)
}
})
}
When you call your service function:
BaseService.sharedInstance.logInUser("email", userPassword: "password",
onSuccess: { responseValue in
},
onFail: { error in
})
Related
I was just wondering what would be the best way to call the same Web Service from Different View Controllers(at different time). What architecture or design should I follow? I don't want to write the same code in each View Controller.
In case of using Alamofire library I can suggest to use
class NetworkManager {
static let shared = NetworkManager()
static let alamofireManager: SessionManager = {
let sessionConfiguration = URLSessionConfiguration.default
sessionConfiguration.timeoutIntervalForRequest = TimeInterval(_TIMEOUT)
sessionConfiguration.timeoutIntervalForResource = TimeInterval(_TIMEOUT)
return Alamofire.SessionManager(configuration: sessionConfiguration)
}()
func performRequest(url: String,
method: HTTPMethod = .get,
parameters: [String: Any] = [String: Any](),
encoding: ParameterEncoding = URLEncoding.default,
contentType: String? = nil,
headers: HTTPHeaders = [String: String](),
success: #escaping(Data, Int) -> (),
failure: #escaping(CustomError) -> ()) {
debugPrint("NetworkManager is calling endpoint: \(url)")
NetworkManager.alamofireManager.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers).validate().response { response in
guard let status = response.response?.statusCode, let data = response.data else {
if let error = response.error {
debugPrint("Error when calling endpoint \(url)")
failure(.unknownError(message: error.localizedDescription))
}
return
}
debugPrint("HTTP Status received: \(status)")
success(data, status)
}
} else {
failure(.noNetworkConnection)
}
}
Please feel free to modify failure handler with your custom error or whatever you like.
Of course then you need to serialise the response.
Create a base class for Alamofire request like that:
import Alamofire
/// Struct for create AlamofireRequestModal
struct AlamofireRequestModal {
/// Struct constant for Alamofire.HTTPMethod
var method: Alamofire.HTTPMethod
/// Struct constant for path
var path: String
/// Struct constant for parameters
var parameters: [String: AnyObject]?
/// Struct constant for encoding:ParameterEncoding
var encoding: ParameterEncoding
/// Struct constant for headers
var headers: [String: String]?
///method to get init
init() {
method = .post
path = ""
parameters = nil
encoding = JSONEncoding() as ParameterEncoding
}
}
///BaseService to call the api's
class BaseService: NSObject {
/// network variable for Reachability
let network = Reachability.init(hostname: "https://www.google.com")
/**
This is method for call WebService into Alamofire
- parameter alamoReq: this is AlamofireRequestModal type request
- parameter success: success response
- parameter failure: failer object
*/
func callWebServiceAlamofire(_ alamoReq: AlamofireRequestModal, success:#escaping ((_ responseObject: AnyObject?) -> Void), failure:#escaping ((_ error: NSError?) -> Void)) {
guard (network?.isReachable)! else {
debugPrint("\n No Network Connection")
return
}
let request = Alamofire.request(alamoReq.path, method: alamoReq.method, parameters: alamoReq.parameters, encoding: alamoReq.encoding, headers: alamoReq.headers)
// Call response handler method of alamofire
request.validate(statusCode: 200..<600).responseJSON(completionHandler: { response in
let statusCode = response.response?.statusCode
if let allHeaderField = response.response {
allHeaderField.setHeaders()
}
switch response.result {
case .success(let data):
if statusCode == 200 {
success(data as AnyObject)
} else {
failure(NSError.init(domain: "www.wen.com", code: 101010, userInfo: ["message": "Something went wrong. Please trt again."]))
}
case .failure(let error):
failure(error as NSError?)
}
})
}
}
Then create a service class according to use, like here I am creating Profile service class for login and registration and profile type all api's are added in this class so you can create multiple serice class according to use:
import Alamofire
///Profile service to call the profile api's
class ProfileService: BaseService {
/**
This is request to BaseService to get Login
- parameter email: User email id
- parameter password: User password to login
- parameter success: success response
- parameter failure: failure response
*/
func doLogin(email: String, password: String, success: #escaping ((_ response: AnyObject?) -> Void), failure: #escaping ((_ error: NSError?) -> Void)) {
var request = AlamofireRequestModal()
request.path = "www.yourpath.com"
request.parameters = ["email": email as AnyObject,
"password": password as AnyObject
]
callWebServiceAlamofire(request, success: success, failure: failure)
}
}
Now, You can call this doLogin method of Profile service from anywhere like Or you can create more layer like Model class and call this service from model class or You can call directly like this:
ProfileService().doLogin(email: "Email", password: "Password", success: { (response) in
// Code here for handle success response
}) { (error) in
// Code here for handle error
}
I would write a network manager class, that takes the web service parameters if any as arguments.
Here is a crude example of the architecture
class YourNetworkManager {
public func callSpecificWebService(argument : Type?, [optional closure to handle results]) {
// Generate the actual URL to be called here. Usually by
// appending a suffix to some constant base url
// The web service call mechanism goes here.
// This could either use the NSURLSession API
// or some third party library such as Alamofire
// Process the generic response conditions from the web service
// here. Pass on the specific parts to the calling method.
}
}
As I mentioned, this is a crude example. The more modularised you can make things, the better it will be.
Never pass your Views and/or ViewControllers to you NetworkManager class.
Suppose you have a NetworkManager class like this.
open class NetworkHelper {
class var sharedManager: NetworkHelper {
struct Static{
static let instance: NetworkHelper = NetworkHelper()
}
return Static.instance
}
func request(_ method: HTTPMethod, _ URLString: String, parameters: [String : AnyObject]? = [:], headers: [String : String]? = [:], completion:#escaping (Any?) -> Void, failure: #escaping (Error?) -> Void) {
let URL = "BASE_PATH" + "URLString"
Alamofire.request(URL, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.responseJSON { response in
switch response.result {
case .success:
completion(response.result.value!)
case .failure(let error):
failure(error)
guard error.localizedDescription == JSON_COULDNOT_SERIALISED else {
return
}
}
}
}
}
Now create a BaseViewController which inherit from UIViewController and write your API call with necessary parameters.
For example in an API call you only need userID as param all else is static.
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func makeThisApiCallWithUserID(userId: Int) {
NetworkHelper.sharedManager.request(.get, "api/v1/yourApiAddress", parameters: ["userId":userId as AnyObject],headers: ["Authorization":"Bearer agshd81tebsf8724j"], completion: { (response) in
}) { (error) in
}
}
}
No you should inherit those ViewController in which you want the same API call and you do not want to write the code again.
class FirstChildViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.makeThisApiCallWithUserID(userId: 123)
}
}
class SecondChildViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.makeThisApiCallWithUserID(userId: 13)
}
}
class ThirdChildViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.makeThisApiCallWithUserID(userId: 3)
}
}
See I haven't write API code in FirstChildViewController, SecondChildViewController, ThirdChildViewController but still they can make the same API call with different parameters.
Do you use Alamofire?, if yes then I have good method for it, written in NetworkHelper Class.
import Foundation
import Alamofire
open class NetworkHelper {
class var sharedManager: NetworkHelper {
struct Static{
static let instance: NetworkHelper = NetworkHelper()
}
return Static.instance
}
func request(_ method: HTTPMethod
, _ URLString: String
, parameters: [String : AnyObject]? = [:]
, headers: [String : String]? = [:]
, onView: UIView?, vc: UIViewController, completion:#escaping (Any?) -> Void
, failure: #escaping (Error?) -> Void) {
let URL = BASE_PATH + URLString
Alamofire.request(URL, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.responseJSON { response in
switch response.result {
case .success:
completion(response.result.value!)
case .failure(let error):
failure(error)
guard error.localizedDescription == JSON_COULDNOT_SERIALISED else {
return
}
}
}
}
}
private func makeRequest<T where T:MappableNetwork>(method method: Alamofire.Method, url: String,
parameters: [String: AnyObject]?, keyPath: String, handler: NetworkHandler<T>.handlerArray) -> Request {
let headers = [
"Authorization": "",
]
return Alamofire
.request(method, url, parameters: parameters, encoding: .URL, headers: headers)
.validate()
.responseArray(keyPath: keyPath) { (response: Alamofire.Response<[T], NSError>) in
if let error = response.result.error {
if let data = response.data {
let error = self.getError(data)
if error != nil {
handler(.Error(error: error!))
return
}
}
handler(.Error(error: error))
} else if let objects = response.result.value {
handler(.Success(data: objects))
}
}
}
I converted code swift 2.x to 3.x and I getting error Type expression is ambiguous without more context.
The error you mentioned tells you that the compiler cannot determine the exact type of the value you entered.
You started with a period, something has to be before the period. Sometimes the compiler can understand without your help. That's not this case, it has several options so it's ambiguous and it asks you to tell exactly the class name that you meant.
I am very new to swift. I have a method like this.
public func prepareUrl (appendString:String,bindedParams:String,isAuthorized:Bool,isGet:Bool,jsonBody:[String:String],completion:#escaping(String)->Void)
{
let baseUrl=Bundle.main.value(forKey: "GlobalURL")
let urlString=baseUrl as! String+appendString as String+bindedParams as String
Alamofire.request(urlString, method: .get, parameters: nil, encoding: JSONEncoding.default)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
print("Progress: \(progress.fractionCompleted)")
}
.validate { request, response, data in
// Custom evaluation closure now includes data (allows you to parse data to dig out error messages if necessary)
return .success
}
.responseJSON { response in
debugPrint(response)
}
}
I have no idea how to call this method since it's having a completion handler part too. How can I call this method. Please help me.
Thanks
To call method like this:
self.prepareUrl(appendString: "www.some.com/api/likeLogin", bindedParams: "name=lee", isAuthorized: false, isGet: true, jsonBody: ["key":"value"]) { (returnString) in
if returnString == "someValue" {
//do something
}
else{
}
}
And in the method, you should call the completion to return value, like:
.responseJSON { response in
completion("aReturnString")
}
Although the method name is prepareUrl, it actually requests the WebApi, so it's better to rename it to request.
Try this :
NOTE :This answer is for example . You need to change as per your needs
func getResponse(url: String, method : HTTPMethod, parameter : [String : AnyObject], Alert : Bool, callback: #escaping responseHandler) -> Void{
Alamofire.request(API_PREFIX + url, method: method, parameters: parameter).validate().responseJSON { response in
switch response.result {
case .success:
if let result = response.result.value {
let JSON = result as! [String : AnyObject]
print("\(JSON as AnyObject)")
callback(JSON as AnyObject, true)
}
case .failure(let error):
print(error)
callback({} as AnyObject, false)
}
}
}
Calling of method using closure
self.getResponse(url: "", method: .post, parameter: ["Email" : "" as AnyObject, "Password" : "" as AnyObject], Alert: true) { (responseObject, success) in
if success{
}
}
I am working on swift 3 application and want to build login system using REST API. First I wanted a way to post data to server (PHP + MYSQL) with parameters so I found this post.
HTTP Request in Swift with POST method
Now I wanted place this code in a method as helper so I can utilise this method from anywhere in app. Hence followed this way:
Where to put reusable functions in IOS Swift?
Current code is as follow:
import Foundation
class Helper {
static func postData(resource: String, params: [String: String]) -> [String:String] {
var request = URLRequest(url: URL(string: "http://localsite.dev/api/\(resource)")!)
request.httpMethod = "POST"
var qryString: String = "?key=abcd"
for (paramKey, paramVal) in params {
qryString = qryString.appending("&\(paramKey)=\(paramVal)")
}
request.httpBody = qryString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("Error")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("Error on HTTP")
return
}
let responseString = String(data: data, encoding: .utf8)
print("success and here is returned data \(responseString)")
}
task.resume()
return ["data" : "some data"]
}
}
Call this using
let loginDetails: [String: String] = ["email": emailTxt.text!, "pass": passTxt.text!]
Helper.postData(resource: "login", params: loginDetails)
In above method rather then printing data I want to return data as per below 4 conditions.
1.If error in request data then I want to return as
[“status”: false, “message”: “Something wrong with request”]
2.If error in HTTP request
[“status”: false, “message”: “Resource not found”]
3.If login fail
[“status”: false, “message”: “Wrong login details”]
4.If login success
[“status”: true, “message”: “Login success”]
If you want to use a third party library for handling HTTP request, I strongly recommend Alamofire.
When I wanna handle HTTP requests I usually create a singleton class:
class HttpRequestHelper {
static let shared = HttpRequestHelper()
func post(to url: URL, params: [String: String], headers: [String: String], completion: (Bool, String) -> ()){
//Make the http request
//if u got a successful response
// parse it to JSON and return it via completion handle
completion(true, message)
//if response is not successful
completion(false, message)
}
}
And you can use it everywhere:
class AnotherClass: UIViewController {
HttpRequestHelper.shared.post(to: url, params: [:], header: [:],
completion:{
success, message in
print(success)
print(message)
})
}
To the POST method more reusable and not just specific to an endpoint, I usually make the completion handler params as Bool, JSON. And then I handle the JSON response from wherever I call the method.
Oh and I use SwiftyJson to parse json, it's the simplest.
I am having an issue when making a POST request to my API via Alamofire, GETs work without issue, however whenever I make a POST when I check the response I get the results of the last GET.
import Alamofire
import SwiftyJSON
class NetworkManager {
static let sharedInstace = NetworkManager()
let defaultManager: Alamofire.Manager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"homestead.app": .DisableEvaluation
]
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders
return Alamofire.Manager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}()
}
internal class ApiHelper {
/**
Get data from a target URL and return JSON data to be parsed
- parameter targetURL: URL to pull data from
- parameter success: return data to the calling function
- parameter failure: return an error message to the calling function
*/
private func getDataFromAPI(targetURL: String, success:(JSONData: JSON) -> (), failure:(message: String) -> ()) {
NetworkManager.sharedInstace.defaultManager.request(.GET, targetURL).responseJSON { response in
print(response.result)
switch response.result {
case .Success:
if let jsonRaw = response.result.value {
let json = JSON(jsonRaw)
success(JSONData: json)
}
case .Failure(let error):
print(error.localizedDescription)
failure(message: error.localizedDescription)
}
}
}
/**
Post data to the target URL and return errors as JSON data to be parsed
- parameter targetURL: URL to post to
- parameter parameters: JSON data to post
- parameter success: return success message to the calling function
- parameter failure: return JSON data to the calling function with server error
*/
private func postDataToAPI(targetURL: String, parameters: [String : AnyObject], success:() -> (), failure:(JSONData: JSON) -> ()) {
NetworkManager.sharedInstace.defaultManager.request(.POST, targetURL, parameters: parameters, encoding: .JSON).responseJSON { response in
debugPrint(response)
success()
}
}
/**
Post an updated profile to the API
- parameter parameters: JSON data to be posted
- parameter success: success callback
- parameter failure: JSON data of serverError
*/
internal func postUpdateRequest(parameters: [String : AnyObject], success:() -> (), failure:(JSONData: JSON) -> ()) {
let url = "https://homestead.app/profile/a/update"
postDataToAPI(url, parameters: parameters, success: {
success()
}, failure: { JSONData in
failure(JSONData: JSONData)
})
}
/**
Get all states from the API
- parameter success: JSON data of all states
- parameter failure: failure message
*/
internal func getAllStates(success:(JSONData: JSON) -> (), failure:(message: String) -> ()) {
let url = "https://homestead.app/api/state/all"
getDataFromAPI(url, success:
{ JSONData in
success(JSONData: JSONData)
}, failure: { message in
failure(message: message)
})
}
}
let api = ApiHelper()
api.getAllStates({ JSONdata in
print(JSONdata)
let params: [String : AnyObject] = ["name" : "Bob"]
api.postUpdateRequest(params, success: { JSONdata in
print("Success")
}, failure: { message in
print("Message")
})
}, failure: { message in
print(message)
})
My code first gets the list of states and then posts an updated user profile. My issue is in that when I get the response for that updated user profile, it includes the response from the earlier GET request that had already been completed. The POST goes through and the changes are made in the web services, but I have no indications in my response object.
I have confirmed the server does not return the list of states when making a POST request, it returns below when called manually from the browser:
{
"success": "Changes saved!"
}
I'm at a loss on why I am getting a response from an earlier request from my POST. Any thoughts?
I figured this out. It turned out I had to add "X-Requested-With": "XMLHttpRequest" to the requests header:
configuration.HTTPAdditionalHeaders = [
"X-Requested-With": "XMLHttpRequest"
]
Now I am getting the responses correctly from the server.