Created
March 3, 2022 19:36
-
-
Save lalkrishna/5576835aa7b2d7e28adb4d8819c0206e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Alamofire | |
import UIKit | |
typealias ResponseType = Decodable | |
typealias AFResponse<Response: Decodable> = Alamofire.DataResponse<ApiResponse<Response>, AFError> | |
struct AFNetwork: NetworkService { | |
static func cancelAllRequests() { | |
AF.cancelAllRequests() | |
} | |
func request<T: ResponseType>(urlRequest: URLRequest, completion: @escaping (CompletionType<T>) -> Void) { | |
request(queue: .main, urlRequest: urlRequest, completion: completion) | |
} | |
func request<T: ResponseType>(queue: DispatchQueue = .main, urlRequest: URLRequest, completion: @escaping (CompletionType<T>) -> Void) { | |
var urlRequest = urlRequest | |
urlRequest.addValue("ios", forHTTPHeaderField: "User-Agent") | |
let dataRequest = AF.request(urlRequest).responseDecodable(queue: queue, decoder: JSONDecoder.custom()) { (response: AFResponse<T>) in | |
self.handleResponse(response, completion: completion) | |
} | |
#if DEBUG | |
dataRequest.responseJSON { json in | |
let statusCode = dataRequest.response?.statusCode ?? 0 | |
if let url = urlRequest.url { | |
AppLogger.log("Req: \(String(describing: url)). StatusCode: \(statusCode)") | |
} | |
if let bodyData = urlRequest.httpBody, let params = String(data: bodyData, encoding: .utf8) { | |
AppLogger.log("PARAMS: \n\(params)") | |
} | |
if let response = json.value { | |
AppLogger.log("Response JSON: \(response)") | |
} | |
} | |
#endif | |
} | |
func uploadMultipartFormData<T: ResponseType>(urlRequest: URLRequest, images: [ImageKey: Data], completion: @escaping (CompletionType<T>) -> Void) { | |
let uploadRequest = AF.upload(multipartFormData: { data in | |
images.forEach { | |
data.append($0.value, withName: $0.key, fileName: "\(UUID().uuidString).jpeg", mimeType: "image/jpeg") | |
} | |
// Attach Params as Multipart data. (Note: Only Top level objects will be encoded. | |
// And JSON will encoded as JSON String.) | |
if let requestBody = urlRequest.httpBody, | |
let object = try? JSONSerialization.jsonObject(with: requestBody, options: .fragmentsAllowed) as? [String: AnyObject] { | |
for (key, value) in object { | |
switch value { | |
case let value as String: | |
data.append(value.data(using: .utf8)!, withName: key) | |
case let value as Int: | |
data.append("\(value)".data(using: .utf8)!, withName: key) | |
case let value as [AnyObject]: | |
for (index, object) in value.enumerated() { | |
data.append("\(object)".data(using: .utf8)!, withName: "\(key)[\(index)]") | |
} | |
default: | |
if JSONSerialization.isValidJSONObject(value) { | |
guard let valueEncoded = try? JSONSerialization.data(withJSONObject: value, options: []) else { | |
return | |
} | |
data.append(valueEncoded, withName: key) | |
} | |
} | |
} | |
} | |
}, with: urlRequest) | |
uploadRequest.responseDecodable { response in | |
self.handleResponse(response, completion: completion) | |
} | |
#if DEBUG | |
uploadRequest.responseJSON { json in | |
if let response = json.value { | |
AppLogger.log("Response JSON: \(response)") | |
} | |
} | |
#endif | |
} | |
// MARK: - Handle | |
private func handleResponse<T: ResponseType>(_ response: AFResponse<T>, completion: @escaping (CompletionType<T>) -> Void) { | |
let statusCode = response.response?.statusCode ?? 0 | |
switch response.result { | |
case .success(let value): | |
guard value.success else { | |
// Error from Server | |
switch statusCode { | |
case 401: | |
AppSession.logout() | |
UIAlertController.defaultAlert(title: Constants.LocalizedString.sessionExpired, message: value.message ?? "", actionTitle: Constants.LocalizedString.login) { _ in | |
UIApplication.changeRootViewController(to: LoginViewController.instantiate().embeddedInNavigationController(), animation: .dismiss) | |
}.show() | |
return | |
default: break | |
} | |
let error = NSError(domain: AppSession.appName, code: statusCode, userInfo: [NSLocalizedDescriptionKey: value.message ?? ""]) | |
completion(.failure(error)) | |
return | |
} | |
guard let result = value.result else { | |
// Api success, but no result object. In this case generally `T` would be `String` | |
if let message = value.message as? T { | |
completion(.success(message)) | |
return | |
} else { | |
let errorMessage = NSLocalizedString("Something wrong happed at server", comment: "") | |
let error = NSError(domain: AppSession.appName, code: statusCode, userInfo: [NSLocalizedDescriptionKey: errorMessage]) | |
completion(.failure(error)) | |
} | |
return | |
} | |
completion(.success(result)) | |
case .failure(let error): | |
#if DEBUG | |
AppLogger.log("API Error: \(error)") | |
#endif | |
completion(.failure(error)) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
typealias Response<T> = (_ result: Result<T, Error>) -> Void | |
struct ApiResponse<T: Decodable>: Decodable { | |
let success: Bool | |
let message: String? | |
let result: T? | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let request = LoginRequest(username: "user", password: "pass") | |
UserRepository().loginUser(request) { result in | |
switch result { | |
case .success(let user): | |
break | |
case .failure(let error): | |
break | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct AFHelper { | |
static func jsonEncode<Parameters: Encodable>(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest { | |
try JSONParameterEncoder(encoder: .custom()).encode(parameters, into: request) | |
} | |
static func urlEncode<Parameters: Encodable>(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest { | |
try URLEncodedFormParameterEncoder(encoder: .custom()).encode(parameters, into: request) | |
} | |
} | |
extension URLEncodedFormEncoder { | |
static func custom() -> URLEncodedFormEncoder { | |
URLEncodedFormEncoder(dateEncoding: .formatted(DateFormatter.iso8601)) | |
} | |
} | |
extension URLRequest { | |
mutating func jsonEncode<Parameters: Encodable>(_ parameters: Parameters?) throws { | |
self = try AFHelper.jsonEncode(parameters, into: self) | |
} | |
mutating func urlEncode<Parameters: Encodable>(_ parameters: Parameters?) throws { | |
self = try AFHelper.urlEncode(parameters, into: self) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public typealias CompletionType<Response> = Result<Response, Error> | |
public typealias ImageKey = String | |
protocol NetworkService { | |
static func cancelAllRequests() | |
func request<Response: ResponseType>(urlRequest: URLRequest, completion: @escaping (Result<Response, Error>) -> Void) | |
func uploadMultipartFormData<T: ResponseType>(urlRequest: URLRequest, images: [ImageKey: Data], completion: @escaping (CompletionType<T>) -> Void) | |
} | |
extension NetworkService { | |
func request<Response: ResponseType>(route: Router, completion: @escaping (Result<Response, Error>) -> Void) { | |
// swiftlint:disable force_try | |
let urlRequest = try! route.asURLRequest() | |
request(urlRequest: urlRequest, completion: completion) | |
} | |
func uploadMultipartFormData<T: ResponseType>(route: Router, images: [ImageKey: Data], completion: @escaping (CompletionType<T>) -> Void) { | |
let urlRequest = try! route.asURLRequest() // swiftlint:disable:this force_try | |
uploadMultipartFormData(urlRequest: urlRequest, images: images, completion: completion) | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protocol Repository { | |
var network: NetworkService { get } | |
init(network: NetworkService) | |
} | |
struct UserRepository: Repository { | |
let network: NetworkService | |
init(network: NetworkService = AFNetwork()) { | |
self.network = network | |
} | |
func loginUser(param: LoginRequest, completion: @escaping Response<User>) { | |
network.request(route: UserAuthRouter.login(param: param), completion: completion) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Router | |
protocol Router { | |
var asURL: URL { get } | |
var path: String { get } | |
var method: String { get } | |
func asURLRequest() throws -> URLRequest | |
} | |
extension Router { | |
var baseURL: URL { | |
ServerConfig().baseURL | |
} | |
var asURL: URL { | |
baseURL.appendingPathComponent(path) | |
} | |
func baseRequest() -> URLRequest { | |
var request = URLRequest(url: asURL) | |
request.httpMethod = method | |
request.setAuthorizationHeader() | |
return request | |
} | |
} | |
extension URLRequest { | |
mutating func setAuthorizationHeader() { | |
guard let token = AppSession.current.userSession.loggedInUser?.token else { return } | |
addValue(token, forHTTPHeaderField: "Authorization") | |
} | |
} | |
// User Auth | |
enum UserAuthRouter: Router { | |
case login(param: LoginRequest) | |
var path: String { | |
switch self { | |
case .login: | |
return "auth/login" | |
} | |
} | |
var method: String { | |
switch self { | |
case .login: | |
return "POST" | |
} | |
} | |
func asURLRequest() throws -> URLRequest { | |
var request = baseRequest() | |
switch self { | |
case .login(let loginRequest): | |
try request.jsonEncode(loginRequest) | |
case .login(let userName, let password): | |
try request.jsonEncode(["user":userName, "password": password]) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment