Upgrading Alamofire to Swift 3 syntax I'm wondering what would be the safest way of upgrading a router based on URLRequestConvertible, (code section currently not conforming). I've tried a few things but my feeling is that the URL encoding might change a little bit.
Although Alamofire seem to have changed extensively the errors i can see directly is that ".GET" triggers a compiler error, as does ParameterCoding.URL that is no longer valid.
The code pattern I have (probably at at least 30 different places in my code) is:
// MARK: - AlamoFire Router
enum Router: URLRequestConvertible
{
static let baseURL = Singleton.sharedInstance.apiURL
static let apiKey: String = Singleton.sharedInstance.apiKey
static let applicationId: String = Singleton.sharedInstance.applicationId
case userPoints()
var method: Alamofire.Method
{
switch self
{
case .userPoints:
return .GET
}
}
var path: String
{
switch self
{
case .userPoints:
return "/users/points/user/\(Singleton.sharedInstance.user.id)"
}
}
var URLRequest: NSMutableURLRequest
{
let URL_val = URL(string: Router.baseURL)!
let mutableURLRequest = NSMutableURLRequest(URL: URL_val.URLByAppendingPathComponent(path))
mutableURLRequest.HTTPMethod = method.rawValue
// Send API key and Application ID in the header
mutableURLRequest.setValue("API-Key apiKey=\(Router.apiKey) applicationId=\(Router.applicationId)", forHTTPHeaderField: "X-Authorization")
let encoding = ParameterEncoding.URL
#if DEBUG
print(mutableURLRequest)
#endif
switch self
{
case .userPoints:
return encoding.encode(mutableURLRequest, parameters: nil).0
}
}
}
Very hopeful someone with insight in Alamofire and Swift 3 could help me figure out a solution to conform to the changes.
Many thanks in advance!
I am familiar with the setup. .Get needs to be changed to .get for one thing.
let URL_val = Router.baseURL.asURL()
var urlRequest = URLRequest(url: URL_val.appendingPathComponent(path))
move the return outside of the switch statement and move it after.
change var urlRequest to func asURLRequest() throws -> URLRequest
switch self {
case .userPoints:
urlRequest = try URLEncoding.default.encode(urlRequest, with: nil)
}
return urlRequest
they have a full working example in their docs under CRUD & Authorization
Related
I want to make use of the RequestAdapter and RequestRetrier protocols, so I created my own so called AuthenticationHandler class which implements both protocols. I do this because the refresh token may be expired so this mechanism comes in handy.
The RequestAdapter protocol method adapt does get called, but the should RequestRetrier protocol method does not. I have a separate class that does the actual request:
class TestRequest {
var authHandler: AuthenticationHandler?
func executeRequest() {
// For testing purposes a false access token is passed
self.authHandler = AuthenticationHandler(accessToken: "some_default_token")
let sessionManager = SessionManager()
sessionManager.adapter = authHandler
sessionManager.retrier = authHandler
var loginModel : LoginMessage = LoginMessage.init()
loginModel.username = "someUserName"
loginModel.password = "WrongPassword"
do {
let binaryData = try loginModel.serializedData()
// Create a file with this binary data in order to use it as part of the multipart formdata
guard let fileURL = createFileFrom(data: binaryData) else {
print("Error creating file")
return
}
// Note: custom headers have been set in the AuthenticationHandler
sessionManager.upload(multipartFormData: { multipartFormData in
multipartFormData.append(fileURL, withName: "content")
},
to: K.endpointLogin) { (encodingResult) in
switch encodingResult{
case .success(let upload, _, _):
upload.responseJSON { response in
print("Encoding result success...")
print("Statuscode: \(response.response?.statusCode)")
print(response)
}
case .failure(let encodingError):
print("Failure: \(encodingError)")
}
}
} catch {
print(error)
}
}
I have followed the example in the documentation here
I have read several previous posts saying it has to do with retaining the sessionManager. But I think that is also covered. My authentication handler looks like this:
class AuthenticationHandler: RequestAdapter, RequestRetrier {
private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: String?) -> Void
private let sessionManager: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
private let lock = NSLock()
private var accessToken: String
private var isRefreshing = false
private var requestsToRetry: [RequestRetryCompletion] = []
init(accessToken: String) {
self.accessToken = accessToken
}
// MARK: - RequestAdapter protocol method
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var urlRequest = urlRequest
if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(K.SERVER_URL) {
urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
urlRequest.setValue("multipart/form-data", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
}
return urlRequest
}
// MARK: - RequestRetrier protocol method
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion) {
lock.lock() ; defer { lock.unlock() }
}
}
My config is as follows:
Alamofire version: 4.7.2
Xcode version: 9.4.1
Swift version: 4
What am I doing wrong here? Why is the request cancelled and is the should method not called?
Any help is greatly appreciated.
Your core issue is that your SessionManager instance is being deinited, which cancels any ongoing tasks. You should keep it around in a singleton or something similar, which will fix the other issue, that of using a new SessionManager for each request, which is an anti pattern.
I am using URLSession's dataTask method with a completion handler. The error in response is nil, but the data object returns something, it returns 0 bytes of data.
I was using Alamofire library firstly, I thought there is something wrong with it because I started using newer version so I stated using my own implementation of Alamofire just so I don't have to rewrite all the calls I already have in my app.
It still returns 0 bytes of data.
When I use the same URL in the Playground with a simple URLSession call, it works and returns the data, do you have any idea what might go wrong?
My implementation of Alamofire (Srsly quick and dirty within 30 minutes):
import Foundation
typealias DataResponse = (data: Data?, response: URLResponse?, error: Error?, request: URLRequest?, result: Result)
public class Alamofire: NSObject {
enum Method: String {
case get = "GET"
case post = "POST"
}
fileprivate static let session = URLSession(configuration: URLSessionConfiguration.default)
public struct request {
var request: URLRequest? = nil
var url: URL? = nil
let method: Alamofire.Method?
let parameters: Parameters?
let headers: Headers?
init(_ request: URLRequest, method: Alamofire.Method? = nil, parameters: Parameters? = nil, headers: Headers? = nil) {
self.request = request
self.url = request.url
self.method = method
self.parameters = parameters
self.headers = headers
}
init(_ url: URL, method: Alamofire.Method? = nil, parameters: Parameters? = nil, headers: Headers? = nil) {
self.url = url
self.request = URLRequest(url: url)
self.method = method
self.parameters = parameters
self.headers = headers
}
}
}
typealias Parameters = [String: Any?]
typealias Headers = [String: String]
extension Alamofire.request {
func responseData(completionHandler: #escaping (_ dataResponse: DataResponse) -> Void) {
guard let request = request else { return }
Alamofire.session.dataTask(with: request) { (data, response, error) in
let result: Result
if let error = error {
result = Result.failure(error)
completionHandler((data, response, error, request, result))
return
}
completionHandler((data, response, error, request, Result.success))
}.resume()
}
}
enum Result {
case success
case failure(Error)
}
So the resolution to my problem was a realisation that the data provider cut me off of data :) Can happen too. That's probably not a "solution" but an option you have to consider as I will from now on :D Thank you all
Did you create an App Transport Security Execption for Allows Arbitrary loads in the info.plist?
I'm using the most recent version of Xcode (8.1 at time of writing), which uses Swift 3.0.
All I'm trying to do is take a string, convert it to a URL and test that URL to see if it gives me a 404 error. I've been able to make a URL and URLRequest by using:
let url = URL(string: fullURL)
let request = URLRequest(url: url!)
but I've found myself unable to get anything working beyond that. I've searched around for help, but most, if not all of it, is written in Swift 2.0, which I've tried to convert to no avail. It seems that even if you change the naming convention to remove the NS prefix, that isn't enough. I tried using:
let response: AutoreleasingUnsafeMutablePointer<URLRequest> = nil
but that gives me an error that "fix-it" makes worse by sticking question marks and semi-colons everywhere.
Apple's documentation isn't helping me much, either. I'm seriously at a loss.
Does anybody know how to correctly set up and test a URL for 404 status in Swift 3.0?
try this out to give you the status codes of the responses - 200, 404 etc:
let url = URL(string: fullURL)
let task = URLSession.shared.dataTask(with: url!) { _, response, _ in
if let httpResponse = response as? HTTPURLResponse {
print(httpResponse.statusCode)
}
}
task.resume()
You could also do the same, simply replacing the with: url! to use the request var as you defined in your example e.g. let task = URLSession.shared.dataTask(with: request) {...} But in this example I don't think you need to really.
Simple example:
let url = // whatever
let session = URLSession.shared
let task = session.downloadTask(with:url) { loc, resp, err in
let status = (resp as! HTTPURLResponse).statusCode
print("response status: \(status)")
}
task.resume()
Here is one more example from delegate method
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask){
let responseStatusCode = (dataTask.response as! HTTPURLResponse).statusCode
}
Here is the example https://github.com/ankitthakur/SwiftNetwork/blob/master/Sources/Shared/SwiftNetwork.swift
I am struggling a lot to set Authorisation token as a connection request property in Alamofire Swift.
I tried sending Auth token in headers and was able to achieve that.
In android we set auth token as HttpUrlConnection.setRequestProperty and the APIs are working fine, but in Swift I have to use same APIs but sending auth token in headers is not helping out.
Options I tried:-
Creating MutableURlRequest and setting HTTPAdditionalHeaders.
var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders ?? [:]
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = defaultHeaders
Please anyone help me sending auth token as a request property rather than a header.
Thanks in advance.
You could send authorization token along with other request's parameters, something like this:
private var parameters: [String: AnyObject]? {
switch self {
case Profile(let token, ...):
return ["authCode" : token, ...]
...
}
}
However it's recommended to be send as header, so just for heads-up, this is how I do it:
import Alamofire
// Helper to build Alamofire Requests
enum APIRouter {
static let baseURL = "http://..."
case Authorize(token: String)
...
}
extension APIRouter: URLRequestConvertible {
var URLRequest: NSMutableURLRequest {
let URLRequest = NSMutableURLRequest(URL: URL)
URLRequest.HTTPMethod = method.rawValue
// Server token is set after authorisation and will be applied to the following requests
if let token = SecureManager.sharedInstance.serverToken {
URLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
let encoding = Alamofire.ParameterEncoding.URL
let request = encoding.encode(URLRequest, parameters: parameters).0
return request
}
private var URL: NSURL {
let baseURL = NSURL(string: APIRouter.baseURL)
return baseURL!.URLByAppendingPathComponent(path)
}
private var path: String {
switch self {
case Authorize:
return "rest/oauth/authorize"
...
}
}
private var method: Alamofire.Method {
switch self {
case Authorize:
return .POST
...
}
}
private var parameters: [String: AnyObject]? {
switch self {
...
}
}
}
I am trying out the Alamofire helpers for networking with my server. I am building up a router for handling my API endpoints. The construction itself seems clear to me, but I am struggling with some SWIFT syntax.
enum Router:URLRequestConvertible {
static let baseURLString = "url"
case AEDS
var URLRequest: NSURLRequest {
let (path: String, parameters: [String: AnyObject]) = {
switch self {
case .AEDS:
let params = [""]
return("/aeds", 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
}
}
I get the message that inside my case .AEDs the params are throwing an error:
[String] is not convertible to [String: AnyObject]
I am kind of new to Swift and could not figure out so far, where to start. I think I provided the array that I am defining. So what does this error mean?
In your switch case, you need to defines params as a dictionary and not as an array.
switch self {
case .AEDS:
let params = [""] <---- This is initialising an array containing a string
return("/aeds", params)
}
Try changing to:
switch self {
case .AEDS:
let params = ["" : ""] <---- This will create a dict
return("/aeds", params)
}
That should solve your problem.