Alamofire: How to conditionally change cache policy based on network status? - swift

The end result I would like to achieve is to use cached data when the network is unavailable and load data from the server when the network is available.
The closest thing I've found is this thread, but I am still having trouble getting it to work using Alamofire.
How to cache response in iOS and show only when internet is not available?
I have two classes, one to check the network status and another that configures an Alamofire url session. Detecting the network status seems to be working, but it's not changing the session configuration. What am I doing wrong? Thanks!
NetworkReachability.shared.startNetworkMonitoring() is run when the app loads in didFinishLaunchingWithOptions.
class NetworkReachability {
static let shared = NetworkReachability()
let reachabilityManager = NetworkReachabilityManager(host: "www.google.com")
func startNetworkMonitoring() {
reachabilityManager?.startListening(onUpdatePerforming: { status in
switch status {
case .notReachable:
ApiManager.configuration.requestCachePolicy = .returnCacheDataDontLoad
case .reachable(.cellular):
ApiManager.configuration.requestCachePolicy = .reloadIgnoringCacheData
case .reachable(.ethernetOrWiFi):
ApiManager.configuration.requestCachePolicy = .reloadIgnoringCacheData
case .unknown:
print("Unknown network state")
}
})
}
}
A custom api manager to configure the cache.
class ApiManager {
static let shared = ApiManager()
static let configuration = URLSessionConfiguration.af.default
public let sessionManager: Alamofire.Session = {
let responseCacher = ResponseCacher(behavior: .modify { _, response in
let userInfo = ["date": Date()]
return CachedURLResponse(
response: response.response,
data: response.data,
userInfo: userInfo,
storagePolicy: .allowed)
})
return Session(
configuration: configuration,
cachedResponseHandler: responseCacher)
}()
}

I'm writing my APIClient here which I've written using Alamofire.
import Alamofire
class NetworkLogger: EventMonitor {
let queue = DispatchQueue(label: "com.ketan.almofire.networklogger")
func requestDidFinish(_ request: Request) {
print(request.description)
}
func request<Value>(
_ request: DataRequest,
didParseResponse response: DataResponse<Value, AFError>
) {
guard let data = response.data else {
return
}
if let json = try? JSONSerialization
.jsonObject(with: data, options: .mutableContainers) {
print(json)
}
}
}
class APIClient {
private var manager = Session()
init() {
configureSession()
}
private func manageCachePolicy() {
if NetworkReachabilityManager()?.isReachable ?? false {
manager.sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
} else {
manager.sessionConfiguration.requestCachePolicy = .returnCacheDataElseLoad
}
}
private func configureSession() {
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .returnCacheDataElseLoad
if NetworkReachabilityManager()?.isReachable ?? false {
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
}
manager = Session(configuration: configuration, eventMonitors: [NetworkLogger()])
///When we don't want network logs
// manager = Session(configuration: configuration)
}
// MARK: - Request
func requestData(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
completion: #escaping (Result<Data, ErrorResult>) -> Void) {
manageCachePolicy()
manager.request(convertible,
method: method,
parameters: parameters,
encoding: encoding,
headers: headers).validate().responseData
{ (response) in
switch response.result {
case .success(let data):
completion(.success(data))
case .failure(let error):
completion(.failure(.network(string: error.localizedDescription)))
}
}
}
}

Related

MVC Networking Swift

I have this Networking class that i declared in the Model .
class Networking {
func response (url : String ) {
guard let url = URL(string: url) else {return}
URLSession.shared.dataTask(with: url, completionHandler: urlPathCompletionHandler(data:response:error:)).resume()
}
func urlPathCompletionHandler (data : Data? , response: URLResponse? , error: Error? ) {
guard let data = data else {return }
do {
let jsondecoder = JSONDecoder()
}catch {
print("Error \(error)")
}
}
}
In the controller . I have an array of users i declared and i want the controller to call from the Model Networking class instead of doing the networking inside the controller. This is part of my controller:
var users = [Users]()
var networking : Networking()
#IBOutlet weak var tableview : UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
}
func getFromModel() {
var vm = networking.response()
}
I want a way of calling the networking class and return an array of users that i can set to the users array above and use it to populate the table view . If i wanted to do that inside the controller it would easy but i am not sure how i can return an array of users from the Model Networking class .
You need to modify your Network class like this:
class Networking {
func response<T: Codable>(url: String, completion: ((T) -> Void)?) {
guard let url = URL(string: url) else {return}
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
self.urlPathCompletionHandler(data: data, response: response, error: error, completion: completion)
}).resume()
}
func urlPathCompletionHandler<T: Codable>(data : Data? , response: URLResponse? , error: Error?, completion: ((T) -> Void)?) {
guard let data = data else { return }
do {
let jsondecoder = JSONDecoder()
// Pseudo Code to decode users
completion?(decodedObject)
} catch {
print("Error \(error)")
}
}
}
And call it like this:
func getFromModel() {
networking.response(url: <#T##String#>) { (users: [User]) in
self.users = users
}
}
OK, there are a few thoughts:
Your response method is performing an asynchronous network request, so you need to give it a completion handler parameter. So, I might suggest something like:
class Networking {
enum NetworkingError: Error {
case invalidURL
case failed(Data?, URLResponse?)
}
private let parsingQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".parsing")
// response method to handle network stuff
func responseData(_ string: String, completion: #escaping (Result<Data, Error>) -> Void) {
guard let url = URL(string: string) else {
completion(.failure(NetworkingError.invalidURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
DispatchQueue.main.async {
if let error = error {
completion(.failure(error))
return
}
guard
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode
else {
completion(.failure(NetworkingError.failed(data, response)))
return
}
completion(.success(responseData))
}
}.resume()
}
// response method to handle the JSON parsing
func response<T: Decodable>(of type: T.Type, from string: String, completion: #escaping (Result<T, Error>) -> Void) {
responseData(string) { result in
switch result {
case .failure(let error):
completion(.failure(error))
case .success(let data):
self.parsingQueue.async {
do {
let responseObject = try JSONDecoder().decode(T.self, from: data)
DispatchQueue.main.async {
completion(.success(responseObject))
}
} catch let parseError {
DispatchQueue.main.async {
completion(.failure(parseError))
}
}
}
}
}
}
}
This obviously assumes that you have some Codable types. For example, it’s common for an API to have some common structure in its responses:
struct ResponseObject<T: Decodable>: Decodable {
let code: Int
let message: String?
let data: T?
}
And maybe the User is like so:
struct User: Decodable {
let id: String
let name: String
}
Then getFromModel (perhaps better called getFromRepository or something like that) could parse it with:
networking.response(of: ResponseObject<[User]>.self, from: urlString) { result in
switch result {
case .failure(let error):
print(error)
case .success(let responseObject):
let users = responseObject.data
// do something with users
}
}
For what it’s worth, if you didn’t want to write your own networking code, you could use Alamofire, and then getFromModel would do:
AF.request(urlString).responseDecodable(of: ResponseObject<[User]>.self) { response in
switch response.result {
case .failure(let error):
print(error)
case .success(let responseObject):
let users = responseObject.data
}
}
Now, clearly the model types are likely to be different in your example, but you didn’t share what your JSON looked like, so I had to guess, but hopefully the above illustrates the general idea. Make a generic-based network API and give it a completion handler for its asynchronous responses.

How can I use Alamofire Router to organize the API call? [swift/ Alamofire5]

I'm trying to convert my AF request to Router structures for a cleaner project. I'm getting an error for:
Value of protocol type 'Any' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols.
Please help me to fix the code. THANK YOU!
My URL will have a placeholder for username and the password will be sent in body. The response will be Bool (success), username and bearer token.
Under is my AF request:
let username = usernameTextField.text
let password = passwordTextField.text
let loginParams = ["password":"\(password)"]
AF.request("https://example.com/users/\(username)/login",
method: .post,
parameters: loginParams,
encoder: JSONParameterEncoder.default,
headers: nil, interceptor: nil).response { response in
switch response.result {
case .success:
if let data = response.data {
do {
let userLogin = try JSONDecoder().decode(UsersLogin.self, from: data)
if userLogin.success == true {
defaults.set(username, forKey: "username")
defaults.set(password, forKey: "password")
defaults.set(userLogin.token, forKey: "token")
print("Successfully get token.")
} else {
//show alert
print("Failed to get token with incorrect login info.")
}
} catch {
print("Error: \(error)")
}
}
case .failure(let error):
//show alert
print("Failed to get token.")
print(error.errorDescription as Any)
}
}
What I have so far for converting to AF Router structures:
import Foundation
import Alamofire
enum Router: URLRequestConvertible {
case login(username: String, password: String)
var method: HTTPMethod {
switch self {
case .login:
return .post
}
}
var path: String {
switch self {
case .login(let username):
return "/users/\(username)/login"
}
}
var parameters: Parameters? {
switch self {
case .login(let password):
return ["password": password]
}
}
// MARK: - URLRequestConvertible
func asURLRequest() throws -> URLRequest {
let url = try Constants.ProductionServer.baseURL.asURL()
var request = URLRequest(url: url.appendingPathComponent(path))
// HTTP Method
request.httpMethod = method.rawValue
// Common Headers
request.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.acceptType.rawValue)
request.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.contentType.rawValue)
// Parameters
switch self {
case .login(let password):
request = try JSONParameterEncoder().encode(parameters, into: request) //where I got the error
}
return request
}
}
class APIClient {
static func login(password: String, username: String, completion: #escaping (Result<UsersLogin, AFError>) -> Void) {
AF.request(Router.login(username: username, password: password)).responseDecodable { (response: DataResponse<UsersLogin, AFError>) in
completion(response.result)
}
}
}
LoginViewController Class (where I replaced the AF.request code)
APIClient.login(password: password, username: username) { result in
switch result {
case .success(let user):
print(user)
case .failure(let error):
print(error.localizedDescription)
}
Codable UsersLogin model
struct UsersLogin: Codable {
let success: Bool
let username: String
let token: String?
enum CodingKeys: String, CodingKey {
case success = "success"
case username = "username"
case token = "token"
}
}
Took me a while but finally fixed it. I also clean up the code too.
enum Router: URLRequestConvertible {
case login([String: String], String)
var baseURL: URL {
return URL(string: "https://example.com")!
}
var method: HTTPMethod {
switch self {
case .login:
return .post
}
}
var path: String {
switch self {
case .login(_, let username):
return "/users/\(username)/login"
}
}
func asURLRequest() throws -> URLRequest {
print(path)
let urlString = baseURL.appendingPathComponent(path).absoluteString.removingPercentEncoding!
let url = URL(string: urlString)
var request = URLRequest(url: url!)
request.method = method
switch self {
case let .login(parameters, _):
request = try JSONParameterEncoder().encode(parameters, into: request)
}
return request
}
}
Usage
let username = usernameTextField.text
AF.request(Router.login(["password": password], username)).responseDecodable(of: UsersLogin.self) { (response) in
if let userLogin = response.value {
switch userLogin.success {
case true:
print("Successfully get token.")
case false:
print("Failed to get token with incorrect login info.")
}
} else {
print("Failed to get token.")
}
}
I solved a similar problem in this way. I created a protocol Routable
enum EncodeMode {
case encoding(parameterEncoding: ParameterEncoding, parameters: Parameters?)
case encoder(parameterEncoder: ParameterEncoder, parameter: Encodable)
}
protocol Routeable: URLRequestConvertible {
var baseURL: URL { get }
var path: String { get }
var method: HTTPMethod { get }
var encodeMode: EncodeMode { get }
}
extension Routeable {
// MARK: - URLRequestConvertible
func asURLRequest() throws -> URLRequest {
let url = baseURL.appendingPathComponent(path)
var urlRequest: URLRequest
switch encodeMode {
case .encoding(let parameterEncoding, let parameters):
urlRequest = try parameterEncoding.encode(URLRequest(url: url), with: parameters)
case .encoder(let parameterEncoder, let parameter):
urlRequest = URLRequest(url: url)
urlRequest = try parameterEncoder.encode(AnyEncodable(parameter), into: urlRequest)
}
urlRequest.method = method
return urlRequest
}
}
And my routers look like this one
enum WifiInterfacesRouter: Routeable {
case listActive(installationId: Int16?)
case insert(interface: WifiInterface)
var encodeMode: EncodeMode {
switch self {
case .listActive(let installationId):
guard let installationId = installationId else {
return .encoding(parameterEncoding: URLEncoding.default, parameters: nil)
}
return .encoding(parameterEncoding: URLEncoding.default, parameters: ["idInstallation": installationId])
case .insert(let interface):
return .encoder(parameterEncoder: JSONParameterEncoder.default, parameter: interface)
}
}
var baseURL: URL {
return URL(string: "www.example.com/wifiInterfaces")!
}
var method: HTTPMethod {
switch self {
case .listActive: return .get
case .insert: return .post
}
}
var path: String {
switch self {
case .listActive: return "listActive"
case .insert: return "manage"
}
}
}
To solve the build error
Protocol 'Encodable' as a type cannot conform to the protocol itself
I used the useful AnyCodable library. A type erased implementation of Codable.
You can't use Parameters dictionaries with Encodable types, as a dictionary of [String: Encodable] is not Encodable, like the error says. I suggest moving that step of the asURLRequest process into a separate function, such as:
func encodeParameters(into request: inout URLRequest) {
switch self {
case let .login(parameters):
request = try JSONParameterEncoder().encode(parameters, into: request)
}
}
Unfortunately this doesn't scale that well for routers with many routes, so I usually break up my routes into small enums and move my parameters into separate types which are combined with the router to produce the URLRequest.

Cache Handling with Moya

We have implemented Moya, RxSwift And Alamofire as pods in our project.
Does anyone know how you gain control of the cache policies per url request using this tech?
I have read through quite a few of the issues on Moya's GitHub page but still can't find anything. Also tried using actual json response stored as files for the sampleData like so:
var sampleData: Data {
guard let path = Bundle.main.path(forResource: "SampleAggregate", ofType: "txt") else {
return "sampleData".utf8Encoded
}
let sample = try? String(contentsOfFile: path, encoding: String.Encoding.utf8)
return sample!.utf8Encoded
}
Thanks in advance for any pro tips :)
As to my understanding, the "cleanest" way to solve this, is to use a custom Moya Plugin. Here's an implementation:
protocol CachePolicyGettable {
var cachePolicy: URLRequest.CachePolicy { get }
}
final class CachePolicyPlugin: PluginType {
public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
if let cachePolicyGettable = target as? CachePolicyGettable {
var mutableRequest = request
mutableRequest.cachePolicy = cachePolicyGettable.cachePolicy
return mutableRequest
}
return request
}
}
To actually use this plugin, there are two required steps left:
The plugin should be added to your Moya provider like this:
let moyaProvider = MoyaProvider<YourMoyaTarget>(plugins: [CachePolicyPlugin()])
YourMoyaTargetshould conform to CachePolicyGettable and thereby define the cache policy for each target:
extension YourMoyaTarget: CachePolicyGettable {
var cachePolicy: URLRequest.CachePolicy {
switch self {
case .sampleTarget, .someOtherSampleTarget:
return .reloadIgnoringLocalCacheData
default:
return .useProtocolCachePolicy
}
}
}
Note: This approach uses a protocol to associate cache policies with target types; one could also realise this via a closure being passed to the plugin. Such closure would then decide which cache policy to use depending on the target type passed as an input parameter to the closure.
Based on #fredpi answer, I slightly improved caching plugin for Moya. Below is my version:
import Foundation
import Moya
protocol CachePolicyGettable {
var cachePolicy: URLRequest.CachePolicy { get }
}
final class NetworkDataCachingPlugin: PluginType {
init (configuration: URLSessionConfiguration, inMemoryCapacity: Int, diskCapacity: Int, diskPath: String?) {
configuration.urlCache = URLCache(memoryCapacity: inMemoryCapacity, diskCapacity: diskCapacity, diskPath: diskPath)
}
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
if let cacheableTarget = target as? CachePolicyGettable {
var mutableRequest = request
mutableRequest.cachePolicy = cacheableTarget.cachePolicy
return mutableRequest
}
return request
}
}
extension NetworkApiService: CachePolicyGettable {
var cachePolicy: URLRequest.CachePolicy {
switch self {
case .getUserProfile:
return .returnCacheDataElseLoad
default:
return .useProtocolCachePolicy
}
}
}
In order to clear the cache, you need to have an access to urlRequest object/objects. How to retrieve an urlRequest for Moya route you can find in the following topic.
To clear the cache you can use following code:
public func clearCache(urlRequests: [URLRequest] = []) {
let provider = ... // your Moya provider
guard let urlCache = provider.manager.session.configuration.urlCache else { return }
if urlRequests.isEmpty {
urlCache.removeAllCachedResponses()
} else {
urlRequests.forEach { urlCache.removeCachedResponse(for: $0) }
}
}
Subclass MoyaProvider and compose requestClosure.
It should look something like:
final class MyProvider<Target: TargetType>: MoyaProvider<Target> {
public init(
endpointClosure: #escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
requestClosure: #escaping RequestClosure = MoyaProvider.defaultRequestMapping,
stubClosure: #escaping StubClosure = MoyaProvider.neverStub,
manager: Manager = MoyaProvider<Target>.defaultAlamofireManager(),
plugins: [PluginType] = [],
trackInflights: Bool = false
) {
super.init(
endpointClosure: endpointClosure,
requestClosure: { endpoint, closure in
var request = try! endpoint.urlRequest() //Feel free to embed proper error handling
if request.url == URL(string: "http://google.com")! {
request.cachePolicy = .returnCacheDataDontLoad
} else {
request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
}
closure(.success(request))
},
stubClosure: stubClosure,
manager: manager,
plugins: plugins,
trackInflights: trackInflights
)
}
}
If you want to disable the stored cookies as well
request.httpShouldHandleCookies = false

Trying to cache with Alamofire with no results

I'm trying to use Alamofire Cache, so my desired behavior would be to load the first time from web and next times if data is present on cache return it without make more requests...after I'll think about expiration...
My code returns always response nil...any suggestions on this? Thanks
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.URLCache = NSURLCache.sharedURLCache()
config.requestCachePolicy = NSURLRequestCachePolicy.ReturnCacheDataElseLoad
let manager = Alamofire.Manager(configuration: config)
manager.request(.GET, url, parameters: parameters, encoding: .URL, headers: ["Token":LocalStorage.read("token") as! String, "Version":"2"]).responseJSON { (response) in
if let json = response.result.value
{
if response.response?.statusCode == 200
{
completionHandler(parser.parseSwifty(JSON(json)), nil)
}
else
{
completionHandler(nil, serverErrorMessage)
}
}
else
{
completionHandler(nil, networkErrorMessage)
}
}
So...I resolved this issue:
in my NetworkHelper I added a computed property like so:
static let manager: Manager = {
let memoryCapacity = 100 * 1024 * 1024; // 100 MB
let diskCapacity = 100 * 1024 * 1024; // 100 MB
let cache = NSURLCache(memoryCapacity: memoryCapacity, diskCapacity: diskCapacity, diskPath: "shared_cache")
let configuration: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = ["Token":LocalStorage.read("token") as! String, "Version":"2"]
configuration.requestCachePolicy = .ReturnCacheDataElseLoad // this is the default
configuration.URLCache = cache
return Manager(configuration: configuration)
}()
and when I have to retrieve some data:
self.manager.request(.GET, url, parameters: parameters, encoding: .URL)
.response { (request, response, data, error) in
debugPrint(request)
debugPrint(response)
debugPrint(error)
}.responseJSON......
I also write a func to preload some data like this:
static func preloadGetRequest(url : String, parameters: Dictionary<String, String>?)
{
self.manager.request(.GET, url, parameters: parameters, encoding: .URL)
.response { (request, response, data, error) in
debugPrint(request)
debugPrint(response)
debugPrint(error)
}
}
Try and let me know :)
It seems the correct way is to create Session Manager early on before calling the request or else the request would always returns nil.
var sessionManager: SessionManager!
override func viewDidLoad() {
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .returnCacheDataElseLoad
sessionManager = SessionManager(configuration: configuration)
}
//later on
#objc private func refreshWeatherData(_ sender: Any) {
// Fetch Weather Data
sessionManager.request("https://jsonplaceholder.typicode.com/posts")
.response { (data) in
self.refreshControl?.endRefreshing()
if let data = data.data {
let json = try? JSON(data: data)
}
}
}
or create a URLRequest with caching policy like so
#objc private func refreshWeatherData(_ sender: Any) {
// Fetch Weather Data
let request = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/posts")!, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 100)
Alamofire.request(request)
.response { (data) in
self.refreshControl?.endRefreshing()
if let data = data.data {
let json = try? JSON(data: data)
}
}
}

Handle timeout with Alamofire

Is it possible to add timeout handler for Alamofire request?
In my project I use Alamofire this way:
init() {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForRequest = 30
self.alamofireManager = Alamofire.Manager(configuration: configuration)
}
func requestAuthorizationWithEmail(email:NSString, password:NSString, completion: (result: RequestResult) -> Void) {
self.alamofireManager!.request(.POST, "myURL", parameters:["email": email, "password":password])
.responseJSON { response in
switch response.result {
case .Success(let JSON):
//do json stuff
case .Failure(let error):
print("\n\nAuth request failed with error:\n \(error)")
completion(result: .ConnectionFailed)
}
}
}
EDIT:
request fail message
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSUnderlyingError=0x7fc10b937320 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey=url, NSErrorFailingURLKey=url, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102, NSLocalizedDescription=The request timed out.}
You can compare error._code and if it is equal to -1001 which is NSURLErrorTimedOut then you know this was a timeout.
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 120
manager.request("yourUrl", method: .post, parameters: ["parameterKey": "value"])
.responseJSON {
response in
switch (response.result) {
case .success: // succes path
case .failure(let error):
if error._code == NSURLErrorTimedOut {
print("Request timeout!")
}
}
}
Swift 3
The accepted answer didn't work for me.
After a lot of research, I did it like this:
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 120
manager.request("yourUrl", method: .post, parameters: ["parameterKey": "value"])
Swift 3, Alamofire 4.5.0
I wanted to set the same timeout for every HTTP call in my project.
The key idea is to declare the Alamofire Session Manager as a global variable. Then to create a URLSessionConfiguration variable, set its timeout in seconds and assign it to the manager.
Every call in the project can use this configured session manager.
In my case the global Alamofire Session Manager variable was set in AppDelegate file (globally) and its configuration was managed in its didFinishLaunchingWithOptions method
AppDelegate.swift
import UIKit
var AFManager = SessionManager()
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 4 // seconds
configuration.timeoutIntervalForResource = 4 //seconds
AFManager = Alamofire.SessionManager(configuration: configuration)
return true
}
...
}
From now the Alamofire request function can be called from any part of the app using the afManager.
For example:
AFManager.request("yourURL", method: .post, parameters: parameters, encoding: JSONEncoding.default).validate().responseJSON { response in
...
}
Swift 3.x
class NetworkHelper {
static let shared = NetworkHelper()
var manager: SessionManager {
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 10
return manager
}
func postJSONData( withParams parameters: Dictionary<String, Any>, toUrl urlString: String, completion: #escaping (_ error: Error,_ responseBody: Dictionary<String, AnyObject>?)->()) {
manager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in
if let error = response.result.error {
if error._code == NSURLErrorTimedOut {
print("Time out occurs!")
}
}
}
}
}
Swift 5, Alamofire 5
The cleanest way I found, that works with the latest version of Alamofire is the following:
AF.request(url).response { (dataResponse: AFDataResponse<Data?>) in
switch dataResponse.result {
case .success(let data):
// succes path
case .failure(let error):
switch error {
case .sessionTaskFailed(URLError.timedOut):
print("Request timeout!")
default:
print("Other error!")
}
}
}
Swift 3.x
Accepted answer didn't worked for me too.
This work for me!
let url = URL(string: "yourStringUrl")!
var urlRequest = URLRequest(url: url)
urlRequest.timeoutInterval = 5 // or what you want
And after:
Alamofire.request(urlRequest).response(completionHandler: { (response) in
/// code here
}
Swift 4
This my way and timeout feature is workable, meanwhile practices singleton for api class.
reference from here
struct AlamofireManager {
static let shared: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 5
let sessionManager = Alamofire.SessionManager(configuration: configuration, delegate: SessionDelegate(), serverTrustPolicyManager: nil)
return sessionManager
}()
}
class Auth {
static let api = Auth()
private init() {}
func headers() -> HTTPHeaders {
return [
"Accept": "XXX",
"Authorization": "XXX",
"Content-Type": "XXX"
]
}
func querySample() {
AlamofireManager.shared.request("api_post_url", method: .post, parameters: ["parametersKey": "value"], encoding: JSONEncoding.default, headers: headers())
.responseJSON(queue: DispatchQueue.global(), options: []) { (response) in
switch response.result {
case .success(let value):
// do your statement
case .failure(let error):
if error._code == NSURLErrorTimedOut {
// timeout error statement
} else {
// other error statement
}
}
})
}
func queryOtherSample() {
AlamofireManager.shared.request("api_get_url", method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers())
.responseJSON(queue: DispatchQueue.global(), options: []) { (response) in
switch response.result {
case .success(let value):
// do your statement
case .failure(let error):
if error._code == NSURLErrorTimedOut {
// timeout error statement
} else {
// other error statement
}
}
})
}
}
For Swift 3.x / Swift 4.0 / Swift 5.0 users with Alamofire >= 5.0
Used request modifier to increase and decrease the timeout interval.
Alamofire's request creation methods offer the most common parameters for customization but sometimes those just aren't enough. The URLRequests created from the passed values can be modified by using a RequestModifier closure when creating requests. For example, to set the URLRequest's timeoutInterval to 120 seconds, modify the request in the closure.
var manager = Session.default
manager.request(urlString, method: method, parameters: dict, headers: headers, requestModifier: { $0.timeoutInterval = 120 }).validate().responseJSON { response in
OR
RequestModifiers also work with trailing closure syntax.
var manager = Session.default
manager.request("https://httpbin.org/get") { urlRequest in
urlRequest.timeoutInterval = 60
urlRequest.allowsConstrainedNetworkAccess = false
}
.response(...)
You can also check it here
Make extension of SessionManager and write a public static variable like this,
"requestTimeOutInterval" this is a public variable. it has time.
extension SessionManager {
public static let custom: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = requestTimeOutInterval
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
}
Swift 5.0, Alamofire 5.4.2
The error code when time out always equal to NSURLErrorTimedOut, so
I try to retrieve Error object from AFError and upcast to NSError.
extension AFError {
var isTimeout: Bool {
if isSessionTaskError,
let error = underlyingError as NSError?,
error.code == NSURLErrorTimedOut //-1001
{
return true
}
return false
}
}
Invoke on response closure.
let request = URLRequest(url: URL(string: "https://httpbin.org/delay/10")!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 2)
AF.request(request).responseString(completionHandler: { response in
switch response.result {
case .success(_):
print("success")
case .failure(let error):
if error.isTimeout {
print("Timeout!")
}
}
})
In Alamofire 5.5 SessionManager has been renamed Session
Here is the documentation link
https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%205.0%20Migration%20Guide.md#breaking-api-changes
Also the example of user
let manager = Alamofire.Session.default
manager.session.configuration.timeoutIntervalForRequest = 15
let headers: HTTPHeaders? = token == nil ? nil : [.authorization(bearerToken: token!),.accept("application/json")]
manager.request(path, method: method, parameters: parameter, headers: headers).responseJSON { (response) in
switch response.result {
case .success:
case .failure:
}
}