ParameterEncoder Problem Updating Alamofire from version 4 to 5 - swift

I am updating my project from Alamofire version 4 to 5. And I am running into a snag. I use to encode my parameters with the follow :
// This struct lets us keep the square brackets in the HTTP GET Request vars to keep the php arrays in tact for the backend
struct BracketGetEncoding: ParameterEncoding {
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try URLEncoding().encode(urlRequest, with: parameters)
request.url = URL(string: request.url!.absoluteString.replacingOccurrences(of: "%5B", with: "[").replacingOccurrences(of: "%5D", with: "]"))
return request
}
}
let request = AF.request(urlString, method: method, parameters: parameters, encoder: BracketGetEncoding(), headers: getRequestHeadersAsHTTPHeaders(), interceptor: nil, requestModifier: nil)
But I am trying to find the equivilant with the update. It now takes a ParameterEncoder instead of a ParameterEncoding.

There are two top level request methods: one that takes the new ParameterEncoder type, and one that takes the older ParameterEncoding type. You can use the old one by changing your encoder parameter to encoding.
I suggest you transition to the ParameterEncoder version when you can, as we're not sure if ParameterEncoding will be supported in the next major version of Alamofire.

Here is the update for people who are looking to do the same thing :
// This struct lets us keep the square brackets in the HTTP GET Request vars to keep the php arrays in tact for the backend
struct BracketParameterEncoder : ParameterEncoder {
func encode<Parameters>(_ parameters: Parameters?, into urlRequest: URLRequest) throws -> URLRequest where Parameters : Encodable {
var request = try URLEncodedFormParameterEncoder.default.encode(parameters, into: urlRequest)
request.url = URL(string: request.url!.absoluteString.replacingOccurrences(of: "%5B", with: "[").replacingOccurrences(of: "%5D", with: "]"))
return request
}
}

Related

Equivalent of Request.serializeResponseJSON - Migration Alamofire 5

I have a problem with the following code:
func request(url: URL, completionHandler: #escaping (AFDataResponse<Any>) -> Void) {
let httpResponse = fakeResponse.response
let data = fakeResponse.data
let error = fakeResponse.error
let result = Request.serializeResponseJSON(options: .allowFragments, response: httpResponse, data: data, error: error)
guard let url = urlBuild(queryType: "q", from: "0", to: "10", uri: nil) else { return }
let urlRequest = URLRequest(url: url)
completionHandler(AFDataResponse(request: urlRequest, response: httpResponse, data: data, result: result))
}
I get the following error with Alamofire 5: Type 'Request' has no member 'serializeResponseJSON'.
I'm new to development with Swift and Alamofire, and I can't find a method equivalent to Request.serializeResponseJSON in Alamofire 5.
Thanks in advance for your help :)
All of the serialize methods from previous versions of Alamofire have been refactored into concrete types. For example, serializeResponseJSON has been replaced by the JSONResponseSerializer, which you can use with the response(responseSerializer:) method. However, for your usage, you shouldn't need to create an instance directly, as you can pass the same parameters to responseJSON. I suggest you use responseDecodable with Decodable types instead of responseJSON, as it will ensure your types are properly parsed.
I also suggest you read through our Usage, Advanced Usage, and API documentation and find some newer tutorials as you start using Alamofire.

How to get original requests in Alamofire 5?

I made a wrapper for Alamofire which makes the data request first and then it prints the details of original URLRequest.
let dataRequest = session.request(url, method: .get, parameters: parameters)
let originalRequest = dataRequest.request
// Now print somehow the details of original request.
It worked fine on Alamofire 4.9, but it stopped in the newest 5.0 version. The problem is that dataRequest.request is nil. Why this behavior has changed? How can I access URLRequest underneath DataRequest?
URLRequests are now created asynchronously in Alamofire 5, so you're not going to be able to access the value immediately. Depending on what you're doing with the URLRequest there may be other solutions. For logging we recommend using the new EventMonitor protocol. You can read our documentation to see more, but adding a simple logger is straightforward:
final class Logger: EventMonitor {
let queue = DispatchQueue(label: ...)
// Event called when any type of Request is resumed.
func requestDidResume(_ request: Request) {
print("Resuming: \(request)")
}
// Event called whenever a DataRequest has parsed a response.
func request<Value>(_ request: DataRequest, didParseResponse response: DataResponse<Value, AFError>) {
debugPrint("Finished: \(response)")
}
}
let logger = Logger()
let session = Session(eventMonitors: [logger])
I had to obtain the URLRequest in a test case. Solved it by adding .responseData and using XCTestExpectation to wait for the async code to return:
func testThatHeadersContainContentEncoding() {
let exp = expectation(description: "\(#function)\(#line)")
let request = AF.request("http://test.org",
method: .post, parameters: ["test":"parameter"],
encoding: GZIPEncoding.default,
headers: ["Other":"Header"])
request.responseData { data in
let myHeader = request.request?.value(forHTTPHeaderField: additionalHeader.dictionary.keys.first!)
// .. do my tests
exp.fulfill()
}
waitForExpectations(timeout: 10, handler: nil)
}

Alamofire 5 Escaping Forward Slashes

Ive been googling and trying for the last few days regarding the automatic escaping of forward slashes of alamofire.
(Where "/path/image.png" becomes "\/path\/image.png")
However all the answers either point towards a solution if your using swiftyJson, sending via a httpBody or using the Alamofire Parameter Class.
https://github.com/SwiftyJSON/SwiftyJSON/issues/440
Im not using SwiftyJson and feel to install the API just to resolve the issue is a case of hitting the nail with a sledge hammer.
Anyway.
My issue is whenever I'm trying to send parameters to an API, Alamofire's
JSONEncoding.default kindly escapes forward slashes. I don't want Alamofire to do this.
In my case I want to send the following parameter and have Alamofire ignore the forward slashes
let parameter : Parameters = ["transaction": "/room/120"]
Alamofire.request(endPoint , method: .post, parameters: parameter ,encoding: JSONEncoding.default , headers: header).validate(statusCode: 200..<300).responseObject { (response: DataResponse<SomeModel>) in
}
Maybe creating a custom json encoder is the way to do this but I find very little documentation on how best to go about it.
I have also tried
let parameter : [String : Any] = ["transaction": "/room/120"]
Alamofire.request(endPoint , method: .post, parameters: parameter ,encoding: JSONEncoding.default , headers: header).validate(statusCode: 200..<300).responseObject { (response: DataResponse<SomeModel>) in
Really appreciate all your help and suggestions.
Ive even read that the backend should be able to deal with the escaping characters, but its working fine for the android dev. Thus if its my code that is sending the wrong data, then I feel it should resolved at the source
Thomas
I had the same issue and decided to go the way of the custom JSON encoder. There are probably better/shorter ways than this but seeing as I'm a Swift noob, it does it's job and is good enough for me.
I simply looked up the used JSONEncoder used by Alamofire and made my own:
public struct JSONEncodingWithoutEscapingSlashes: ParameterEncoding {
// MARK: Properties
/// Returns a `JSONEncoding` instance with default writing options.
public static var `default`: JSONEncodingWithoutEscapingSlashes { return JSONEncodingWithoutEscapingSlashes() }
/// Returns a `JSONEncoding` instance with `.prettyPrinted` writing options.
public static var prettyPrinted: JSONEncodingWithoutEscapingSlashes { return JSONEncodingWithoutEscapingSlashes(options: .prettyPrinted) }
/// The options for writing the parameters as JSON data.
public let options: JSONSerialization.WritingOptions
// MARK: Initialization
/// Creates a `JSONEncoding` instance using the specified options.
///
/// - parameter options: The options for writing the parameters as JSON data.
///
/// - returns: The new `JSONEncoding` instance.
public init(options: JSONSerialization.WritingOptions = []) {
self.options = options
}
// MARK: Encoding
/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
/// - parameter urlRequest: The request to have parameters applied.
/// - parameter parameters: The parameters to apply.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue)?.replacingOccurrences(of: "\\/", with: "/")
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = string!.data(using: .utf8)
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
/// Creates a URL request by encoding the JSON object and setting the resulting data on the HTTP body.
///
/// - parameter urlRequest: The request to apply the JSON object to.
/// - parameter jsonObject: The JSON object to apply to the request.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let jsonObject = jsonObject else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options)
let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue)?.replacingOccurrences(of: "\\/", with: "/")
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = string!.data(using: .utf8)
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
}
Probably should include some more error handling too :)
Finally I could use it like the standard JSONEncoder:
Alamofire.request(EndpointsUtility.sharedInstance.cdrStoreURL, method: .post, parameters: payload, encoding: JSONEncodingWithoutEscapingSlashes.prettyPrinted)...
hope it helps!

EXC_BAD_ACCESS Error In App, Alamofire 3.1.2

I'm running Xcode 7.1, with an iOS device on 9.1, and the latest version of Alamofire, I believe it's 3.1.2.
I've seen others with similar errors - this one from SO and this one from their Github issues page - to mine, but the resolutions did not work for me. The resolutions that worked in these scenarios seemed to be deleting the DerivedData folders, but that doesn't work in my situation.
The issue is that I can make web requests with Alamofire when I run in the simulator, but I get the following error (in the image) when I install the application on my device.
I've tried using a device running 9.1, 9.0.1 and 8.3. All of these devices crash when I attempt to make the web request. I'm stuck on this and I've made no headway. This worked until I updated all my pods in my podfile and my Xcode version a couple of nights ago.
I'm a newbie at iOS development, so if you need to see more error logs or things of that nature, let me know and I'll do my best to get you those. Any help is appreciated.
Edit: Adding code for the Alamofire.request method as requested. Also adding code for the referenced Manager.sharedInstance.request method. These are methods within the Alamofire library itself.
// Alamofire.request
public func request(
method: Method,
_ URLString: URLStringConvertible,
parameters: [String: AnyObject]? = nil,
encoding: ParameterEncoding = .URL,
headers: [String: String]? = nil)
-> Request
{
return Manager.sharedInstance.request(
method,
URLString,
parameters: parameters,
encoding: encoding,
headers: headers
)
}
// Manager.sharedInstance.request
public func request(
method: Method,
_ URLString: URLStringConvertible,
parameters: [String: AnyObject]? = nil,
encoding: ParameterEncoding = .URL,
headers: [String: String]? = nil)
-> Request
{
let mutableURLRequest = URLRequest(method, URLString, headers: headers)
let encodedURLRequest = encoding.encode(mutableURLRequest, parameters: parameters).0
return request(encodedURLRequest)
}
One problem is that stringByAddingPercentEncodingWithAllowedCharacters returns an optional, and when you do string interpolation, you're going to get Optional(...) appearing in the string. Thus when it tries to create a URL from that, it's undoubtedly failing. Print the uri string and you'll see the issue. E.g., if the searchTerm was "foo bar", the uri would become:
https://api.spotify.com/v1/search&q=Optional("foo%20bar")&type=artist,album,track
But that's not correct. Add a ! to the end of the expression to unwrap it:
let searchTermEncoded = searchTerm.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
And then the URI will be (largely) correct.
--
I'd suggest not doing this percent escaping yourself (as the character set you're using is not correct, even once you do properly unwrap searchTermEncoded). Let Alamofire do this for you:
let urlString = "https://api.spotify.com/v1/search"
let parameters = ["q" : searchTerm, "type" : "artist,album,track"]
Alamofire.request(.GET, urlString, parameters: parameters)
.responseJSON { response in
guard response.result.error == nil else {
print(response.result.error)
return
}
print(response.result.value)
}

HTTP Request error using Alamofire

I am trying to access my MAMP database webservice using Alamofire. Following is my code:
Following is my router to construct my URL:
enum Router: URLRequestConvertible {
static let baseURLString = "http://pushchat.local:44447/"
case PostJoinRequest(String,String,String,String,String)
var URLRequest: NSURLRequest {
let (path: String, parameters: [String: AnyObject]) = {
switch self {
case .PostJoinRequest (let addPath, let userID, let token, let nickName, let secretCode):
let params = ["cmd": "join", "user_id": "\(userID)", "token": "\(token)", "name": "\(nickName)", "code": "\(secretCode)"]
return (addPath, params)
}
}()
let URL = NSURL(string: Router.baseURLString)
let URLRequest = NSURLRequest(URL: URL!.URLByAppendingPathComponent(path))
let encoding = Alamofire.ParameterEncoding.URL
return encoding.encode(URLRequest, parameters: parameters).0
}
}
Following is my viewdidload code:
Alamofire.request(.POST,Router.PostJoinRequest("api.php","12345678901234","12345678901234","ABCDEF","TopSecret")).responseJSON()
{(request, response, JSON, error) in
println(JSON)
}
Following is the compile error:
Cannot invoke 'responseJSON' with an argument list of type '((,,,)->_)'
Following is the declaration from Almofire for your reference.
:param: method The HTTP method.
:param: URLString The URL string.
:param: parameters The parameters. `nil` by default.
:param: encoding The parameter encoding. `.URL` by default.
:returns: The created request.
*/
// public func request(method: Method, _ URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request {
// return request(encoding.encode(URLRequest(method, URLString), parameters: parameters).0)
// }
Please let me know why am I facing this issue while chaining and what is it that I am not doing right?
Thanks for your help.
Dev
The compiler error message is really misleading – there is no problem with responseJSON but with request method itself.
In fact compiler does not like your second parameter. You are passing URLRequestConvertible but URLStringConvertible is expected (see the signature you posted).
Maybe you wanted to call another version of request method:
//:param: URLRequest The URL request
//:returns: The created request.
public func request(URLRequest: URLRequestConvertible) -> Request
In that case you have to adjust your Router class and set HTTP method into NSURLRequest created inside. For example:
let URLRequest = NSMutableURLRequest(URL: URL!.URLByAppendingPathComponent(path))
URLRequest.HTTPMethod = "POST"
Note you will also probably need to use another parameter/data encoding.