Alamofire with Custom Parameter Encoding for Swift Application

Alamofire with custom parameter encoding for swift application

The custom closure is not executed by design, because there are no parameters to encode. He're a code excerpt taken from Alamofire.swift source file:

if parameters == nil {
return (URLRequest.URLRequest, nil)
}

As you can see, you can bybass this condition by passing an empty dictionary:

Alamofire.request(.POST, WebServiceURLString, parameters: Dictionary(), encoding: .Custom(custom))

The custom closure will now be executed.

Alamofire Custom Parameter Encoding

You have a couple questions in here. Let's break them down 1x1.

Compiler Issue

Your compiler issue is due to the fact that your return tuple is the wrong type. In Alamofire 1.3.0, we changed the return type to be an NSMutableURLRequest which ends up making things much easier overall. That should fix your compiler issue.

Setting the HTTPBody

Now you have a couple options here.

Option 1 - Encode Data as JSON

let options = NSJSONWritingOptions()
let data = try NSJSONSerialization.dataWithJSONObject(parameters!, options: options)

mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
mutableURLRequest.HTTPBody = data

From what you posted I'm assuming you actually want .URL encoding for the parameters.

Option 2 - Use the .URL Case

let parameters: [String: AnyObject] = [:] // fill in
let encodableURLRequest = NSURLRequest(URL: URL)
let encodedURLRequest = ParameterEncoding.URL.encode(encodableURLRequest, parameters).0

let mutableURLRequest = NSMutableURLRequest(URL: encodedURLRequest.URLString)
mutableURLRequest.HTTPMethod = "POST"
mutableURLRequest.setValue("text/html; charset=utf-8", forHTTPHeaderField: "Content-Type")

Alamofire.request(mutableURLRequest)
.response { request, response, data, error in
print(request)
print(response)
print(error)
}

Hopefully that helps get you going. Best of luck!

Alamofire 3 Custom Encoding To Alamofire 4 Custom Encoding

In Alamofire 4.0 you should use ParameterEncoding.

struct CustomEncoding: ParameterEncoding {
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try! URLEncoding().encode(urlRequest, with: parameters)
let urlString = request.url?.absoluteString.replacingOccurrences(of: "%5B%5D=", with: "=")
request.url = URL(string: urlString!)
return request
}
}

Alamofire 4 Swift 3 ParameterEncoding Custom

In Alamofire 4.0 you should use ParameterEncoding protocol. Here is an example, which makes any String UTF8 encodable.

extension String: ParameterEncoding {

public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try urlRequest.asURLRequest()
request.httpBody = data(using: .utf8, allowLossyConversion: false)
return request
}

}

Alamofire.request("http://mywebsite.com/post-request", method: .post, parameters: [:], encoding: "myBody", headers: [:])

how to use Alamofire with custom headers

According to the official documentation, modifying the session configuration is not recommended:

This is not recommended for Authorization or Content-Type headers.
Instead, use URLRequestConvertible and ParameterEncoding,
respectively.

So an example usage of URLRequestConvertible for authorization would be:

enum Router: URLRequestConvertible {
static let baseUrlString = "some url string"

case Get(query: String)

var URLRequest: NSMutableURLRequest {
let (path: String, parameters: [String: AnyObject]?) = {
switch self {
case .Get(let query):
return ("/get", ["q": query])
}
}()

let URL = NSURL(string: Router.baseUrlString)!
let URLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path))
// set header fields
URLRequest.setValue("a", forHTTPHeaderField: "Authorization")

let encoding = Alamofire.ParameterEncoding.URL
return encoding.encode(URLRequest, parameters: parameters).0
}
}

and when you want to make a request:

Manager.sharedInstance.request(Router.Get(query: "test"))

More info about URLRequestConvertible: https://github.com/Alamofire/Alamofire#urlrequestconvertible

Old Answer

As of Alamofire v1.0 Pers answer no longer works. In the new version additional headers should be added to the HTTPAdditionalHeaders property of NSURLSessionConfiguration

Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders = ["Authorization": authorizationToken]

More info here: https://github.com/Alamofire/Alamofire/issues/111

iOS create generic Alamofire request using swift

Git link: https://github.com/sahilmanchanda2/wrapper-class-for-alamofire

Here is my version(Using Alamofire 5.0.2):

import Foundation
import Alamofire

class NetworkCall : NSObject{

enum services :String{
case posts = "posts"
}
var parameters = Parameters()
var headers = HTTPHeaders()
var method: HTTPMethod!
var url :String! = "https://jsonplaceholder.typicode.com/"
var encoding: ParameterEncoding! = JSONEncoding.default

init(data: [String:Any],headers: [String:String] = [:],url :String?,service :services? = nil, method: HTTPMethod = .post, isJSONRequest: Bool = true){
super.init()
data.forEach{parameters.updateValue($0.value, forKey: $0.key)}
headers.forEach({self.headers.add(name: $0.key, value: $0.value)})
if url == nil, service != nil{
self.url += service!.rawValue
}else{
self.url = url
}
if !isJSONRequest{
encoding = URLEncoding.default
}
self.method = method
print("Service: \(service?.rawValue ?? self.url ?? "") \n data: \(parameters)")
}

func executeQuery<T>(completion: @escaping (Result<T, Error>) -> Void) where T: Codable {
AF.request(url,method: method,parameters: parameters,encoding: encoding, headers: headers).responseData(completionHandler: {response in
switch response.result{
case .success(let res):
if let code = response.response?.statusCode{
switch code {
case 200...299:
do {
completion(.success(try JSONDecoder().decode(T.self, from: res)))
} catch let error {
print(String(data: res, encoding: .utf8) ?? "nothing received")
completion(.failure(error))
}
default:
let error = NSError(domain: response.debugDescription, code: code, userInfo: response.response?.allHeaderFields as? [String: Any])
completion(.failure(error))
}
}
case .failure(let error):
completion(.failure(error))
}
})
}
}

The above class uses latest Alamofire version (as of now Feb 2020), This class covers almost every HTTP Method with option to send data in Application/JSON format or normal. With this class you get a lot of flexibility and it automatically converts response to your Swift Object.

Look at the init method of this class it has:

  1. data: [String,Any] = In this you will put your form data.

  2. headers: [String:String] = In this you can send custom headers that you want to send along with the request

  3. url = Here you can specify full url, you can leave it blank if you already have defined baseurl in Class. it comes handy when you want to consume a REST service provided by a third party. Note: if you are filling the url then you should the next parameter service should be nil

  4. service: services = It's an enum defined in the NetworkClass itself. these serves as endPoints. Look in the init method, if the url is nil but the service is not nil then it will append at the end of base url to make a full URL, example will be provided.

  5. method: HTTPMethod = here you can specify which HTTP Method the request should use.

  6. isJSONRequest = set to true by default. if you want to send normal request set it to false.

In the init method you can also specify common data or headers that you want to send with every request e.g. your application version number, iOS Version etc

Now Look at the execute method: it's a generic function which will return swift object of your choice if the response is success. It will print the response in string in case it fails to convert response to your swift object. if the response code doesn't fall under range 200-299 then it will be a failure and give you full debug description for detailed information.

Usage:

say we have following struct:

struct Post: Codable{
let userId: Int
let id: Int
let title: String
let body: String
}

Note the base url defined in NetworkClass https://jsonplaceholder.typicode.com/

Example 1: Sending HTTP Post with content type Application/JSON

let body: [String : Any] = ["title": "foo",
"body": "bar",
"userId": 1]
NetworkCall(data: body, url: nil, service: .posts, method: .post).executeQuery(){
(result: Result<Post,Error>) in
switch result{
case .success(let post):
print(post)
case .failure(let error):
print(error)
}
}

output:

Service: posts 
data: ["userId": 1, "body": "bar", "title": "foo"]
Post(userId: 1, id: 101, title: "foo", body: "bar")

  1. HTTP 400 Request

    NetworkCall(data: ["email":"peter@klaven"], url: "https://reqres.in/api/login", method: .post, isJSONRequest: false).executeQuery(){
    (result: Result) in
    switch result{
    case .success(let post):
    print(post)
    case .failure(let error):
    print(error)
    }
    }

output:

Service: https://reqres.in/api/login 
data: ["email": "peter@klaven"]
Error Domain=[Request]: POST https://reqres.in/api/login
[Request Body]:
email=peter%40klaven
[Response]:
[Status Code]: 400
[Headers]:
Access-Control-Allow-Origin: *
Content-Length: 28
Content-Type: application/json; charset=utf-8
Date: Fri, 28 Feb 2020 05:41:26 GMT
Etag: W/"1c-NmpazMScs9tOqR7eDEesn+pqC9Q"
Server: cloudflare
Via: 1.1 vegur
cf-cache-status: DYNAMIC
cf-ray: 56c011c8ded2bb9a-LHR
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
x-powered-by: Express
[Response Body]:
{"error":"Missing password"}
[Data]: 28 bytes
[Network Duration]: 2.2678009271621704s
[Serialization Duration]: 9.298324584960938e-05s
[Result]: success(28 bytes) Code=400 "(null)" UserInfo={cf-ray=56c011c8ded2bb9a-LHR, Access-Control-Allow-Origin=*, Date=Fri, 28 Feb 2020 05:41:26 GMT, expect-ct=max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct", Server=cloudflare, Etag=W/"1c-NmpazMScs9tOqR7eDEesn+pqC9Q", x-powered-by=Express, Content-Type=application/json; charset=utf-8, Content-Length=28, Via=1.1 vegur, cf-cache-status=DYNAMIC}

  1. with custom headers

    NetworkCall(data: ["username":"sahil.manchanda2@gmail.com"], headers: ["custom-header-key" : "custom-header-value"], url: "https://httpbin.org/post", method: .post).executeQuery(){(result: Result) in
    switch result{
    case .success(let data):
    print(data)
    case .failure(let error):
    print(error)
    }
    }

output:

Service: https://httpbin.org/post 
data: ["username": "sahil.manchanda2@gmail.com"]
{
"args": {},
"data": "{\"username\":\"sahil.manchanda2@gmail.com\"}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8",
"Accept-Language": "en;q=1.0",
"Content-Length": "41",
"Content-Type": "application/json",
"Custom-Header-Key": "custom-header-value",
"Host": "httpbin.org",
"User-Agent": "NetworkCall/1.0 (sahil.NetworkCall; build:1; iOS 13.2.2) Alamofire/5.0.2",
"X-Amzn-Trace-Id": "Root=1-5e58a94f-fab2f24472d063f4991e2cb8"
},
"json": {
"username": "sahil.manchanda2@gmail.com"
},
"origin": "182.77.56.154",
"url": "https://httpbin.org/post"
}

typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil))

In the last example you can see typeMismatch at the end, I tried to pass [String:Any] in the executeQuery but since the Any doesn't confirm to encodable I had to use String.



Related Topics



Leave a reply



Submit