Search code examples
swiftgenericsalamofire

How can I write a generic wrapper for alamofire request in swift?


How can I write a generic wrapper for alamofire request ?

How can I convert the POST and GET function to Generic function in the following code?

I need to have generic request functions that show different behaviors depending on the type of data received.

Also, Can the response be generic?

My non-generic code is fully outlined below

import Foundation
import Alamofire
import SwiftyJSON
// for passing string body
extension String: ParameterEncoding {
    public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
        var request = try urlRequest.asURLRequest()
        request.httpBody = data(using: .utf8, allowLossyConversion: false)
        return request
    }
}
public class ConnectionManager {
    func Post (FirstName: String, LastName: String, NationalID: String, NationalCode: String, BirthDate: Date,Image: String,isFemale: Bool,Age:Int64,Avg:Double, completion: @escaping CompletionHandler) {
        let body: [String:Any] = [
            "FirstName":FirstName,
            "LastName": LastName,
            "NationalID": NationalID,
            "NationalCode": NationalCode,
            "BirthDate":BirthDate,
            "Image": Image,
            "isFemale": isFemale,
            "Age": Age,
            "Avg": Avg
        ]
        Alamofire.request(BASE_URL, method: .post, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseJSON { (response) in
            if response.response?.statusCode == 200 {
                guard let data = response.result.value else { return }
                print(data)
                completion(true)
            } else {
                print("error reg auth service")
                guard let er = response.result.value else { return }
                print(er)
                completion(false)
                debugPrint(response.result.error as Any)
            }
        }
    }
    func get(FirstName: String, LastName: String, NationalID: String, NationalCode: String, BirthDate: Date,Image: String,isFemale: Bool,Age:Int64,Avg:Double, completion: @escaping CompletionHandler) -> [Person] {
        let body: [String:Any] = [
            "FirstName":FirstName,
            "LastName": LastName,
            "NationalID": NationalID,
            "NationalCode": NationalCode,
            "BirthDate":BirthDate,
            "Image": Image,
            "isFemale": isFemale,
            "Age": Age,
            "Avg": Avg
        ]
        Alamofire.request(BASE_URL, method: .get, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseJSON { (response) in
            if response.response?.statusCode == 200 {
                print("no error login in authservice")
                guard let data = response.result.value else { return }
                print(data)
                completion(true)
            }
            else if response.response?.statusCode == 400  {
                print("error 400 login in authservice")
                guard let er = response.result.value else { return }
                print(er)
                debugPrint(response.result.error as Any)
                completion(false)
            } else {
                print("error ## login in authservice")
                guard let er = response.result.value else { return }
                print(er)
                debugPrint(response.result.error as Any)
                completion(false)
            }
        }
        return [Person]()
    }
}

Solution

  • The best idea is to use the URLRequestConvertible Alamofires protocol and create your own protocol and simple structs for every API request:

    
    import Foundation
    import Alamofire
    
    protocol APIRouteable: URLRequestConvertible {
        var baseURL: String { get }
        var path: String { get }
        var method: HTTPMethod { get }
        var parameters: Parameters? { get }
        var encoding: ParameterEncoding { get }
    }
    
    extension APIRouteable {
        var baseURL: String { return URLs.baseURL }
        // MARK: - URLRequestConvertible
        func asURLRequest() throws -> URLRequest {
            let url = try baseURL.asURL().appendingPathComponent(path)
            var urlRequest = URLRequest(url: url)
            urlRequest.httpMethod = method.rawValue
            return try encoding.encode(urlRequest, with: parameters)
        }
    }
    

    and request can look like this:

    struct GetBooks: APIRouteable {
            var path: String
            var method: HTTPMethod
            var parameters: Parameters?
            var encoding: ParameterEncoding
        }
    

    and inside the APIClient prepare the generic method:

    func perform<T: Decodable>(_ apiRoute: APIRouteable,
                     completion: @escaping (Result<T>) -> Void) {
            let dataRequest = session
                .request(apiRoute)
            dataRequest
                .validate(statusCode: 200..<300)
                .responseDecodable(completionHandler: {  [weak dataRequest] (response: DataResponse<T>) in
                    dataRequest.map { debugPrint($0) }
                    let responseData = response.data ?? Data()
                    let string = String(data: responseData, encoding: .utf8)
                    print("Repsonse string: \(string ?? "")")
                    switch response.result {
                    case .success(let response):
                        completion(.success(response))
                    case .failure(let error):
                        completion(.failure(error))
                    }
                })
        }
    

    and use it:

     func getBooks(completion: @escaping (Result<[Book]>) -> Void) {
            let getBooksRoute = APIRoute.GetBooks()
            perform(getBooksRoute, completion: completion)
        }