Skip to content

Instantly share code, notes, and snippets.

@lalkrishna
Created March 3, 2022 19:36
Show Gist options
  • Save lalkrishna/5576835aa7b2d7e28adb4d8819c0206e to your computer and use it in GitHub Desktop.
Save lalkrishna/5576835aa7b2d7e28adb4d8819c0206e to your computer and use it in GitHub Desktop.
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))
}
}
}
typealias Response<T> = (_ result: Result<T, Error>) -> Void
struct ApiResponse<T: Decodable>: Decodable {
let success: Bool
let message: String?
let result: T?
}
let request = LoginRequest(username: "user", password: "pass")
UserRepository().loginUser(request) { result in
switch result {
case .success(let user):
break
case .failure(let error):
break
}
}
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)
}
}
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)
}
}
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)
}
}
// 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