import UIKit class ViewController: UIViewController { let service: WallPostable = BasicWallAPI() @IBOutlet weak var textField: UITextField! @IBAction func postAction() { service.postWall(with: textField.text!) } }
enum HTTPMethod: String { case GET case POST case PUT case DELETE } protocol HTTPRequestRepresentable { var path: String { get set } var httpMethod: HTTPMethod { get } var parameters: JSON? { get set } var headerFields: [String: String]? { get set } var bodyString: String? { get set } } extension HTTPRequestRepresentable { func urlRequest() -> URLRequest? { guard var urlComponents = URLComponents(string: self.path) else { return nil } if let parametersJSON = self.parameters { var queryItems = [URLQueryItem]() for (key, value) in parametersJSON { queryItems.append(URLQueryItem(name: key, value: value as? String)) } urlComponents.queryItems = queryItems } guard let url = urlComponents.url else { return nil } var urlRequest = URLRequest(url: url) urlRequest.httpMethod = self.httpMethod.rawValue urlRequest.allHTTPHeaderFields = headerFields if let body = bodyString { urlRequest.httpBody = body.data(using: .utf8) } return urlRequest } }
enum Result<T, E> { case Value(T) case Error(E) } protocol ResponseHandler { associatedtype ResultType associatedtype ErrorType func handleResponse(_ response: ResponseRepresentable, completion: (Result<ResultType, ErrorType>) -> ()) }
protocol RequestPreparator { func prepareRequest(_ request: inout HTTPRequestRepresentable) }
protocol SuccessResponseChecker { func isSuccessResponse(_ response: ResponseRepresentable) -> Bool }
protocol ErrorHandler { var errorCodeHandler: ErrorCodeHandler { get set } func handleError(_ error: ErrorRepresentable) }
protocol DecodingProcessor { associatedtype DecodingResult func decodeFrom(_ data: Data) throws -> DecodingResult }
protocol Service { associatedtype ResultType: Decodable associatedtype ErrorType: ErrorRepresentable typealias SuccessHandlerBlock = (ResultType) -> () typealias FailureHandlerBlock = (ErrorType) -> () var request: HTTPRequestRepresentable? { get set } var responseHandler: HTTPResponseHandler<ResultType, ErrorType>? { get set } func sendRequest() -> Self? }
final class BaseService<T: Decodable, E: ErrorRepresentable>: Service { typealias ResultType = T typealias ErrorType = E var responseHandler: HTTPResponseHandler<T, E>? = HTTPResponseHandler<T, E>() var request: HTTPRequestRepresentable? var successHandler: SuccessHandlerBlock? var failureHandler: FailureHandlerBlock? var noneHandler: (() -> ())? var requestPreparator: RequestPreparator? = BaseRequestPreparator() private var session: URLSession { let session = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: nil) return session } @discardableResult func sendRequest() -> BaseService<T, E>? { guard var request = request else { return nil } requestPreparator?.prepareRequest(&request) guard let urlRequest = request.urlRequest() else { return nil } session.dataTask(with: urlRequest) { [weak self] (data, response, error) in let response = BaseResponse(data: data, response: response, error: error) self?.responseHandler?.handleResponse(response, completion: { [weak self] (result) in switch result { case let .Value(model): self?.processSuccess(model) case let .Error(error): self?.processError(error) } }) }.resume() return self } @discardableResult func onSucces(_ success: @escaping SuccessHandlerBlock) -> BaseService<T, E> { successHandler = success return self } @discardableResult func onFailure(_ failure: @escaping FailureHandlerBlock) -> BaseService<T, E> { failureHandler = failure return self } private func processSuccess(_ model: T) { successHandler?(model) successHandler = nil } private func processError(_ error: E) { failureHandler?(error) failureHandler = nil } }
class HTTPResponseHandler<T: Decodable, E: ErrorRepresentable>: ResponseHandler { typealias ResultType = T typealias ErrorType = E private var isResponseRepresentSimpleType: Bool { return T.self == Int.self || T.self == String.self || T.self == Double.self || T.self == Float.self } var errorHandler: ErrorHandler = BaseErrorHandler() var successResponseChecker: SuccessResponseChecker = BaseSuccessResponseChecker() var decodingProcessor = ModelDecodingProcessor<T>() var nestedModelGetter: NestedModelGetter? func handleResponse(_ response: ResponseRepresentable, completion: (Result<T, E>) -> ()) { if successResponseChecker.isSuccessResponse(response) { processSuccessResponse(response, completion: completion) } else { processFailureResponse(response, completion: completion) } } private func processSuccessResponse(_ response: ResponseRepresentable, completion: (Result<T, E>) -> ()) { guard var data = response.data else { return } // , guard let result = try? decodingProcessor.decodeFrom(data) else { completion(Result.Error(E(ProcessingErrorType.modelProcessingError))) return } completion(.Value(result)) } private func simpleTypeUsingNestedModelGetter(from data: Data) -> T? { let getter = nestedModelGetter! guard let escapedModelJSON = try? getter.getFrom(data) else { return nil } guard let result = escapedModelJSON[getter.escapedModelKey] as? T else { return nil } return result } private func processFailureResponse(_ response: ResponseRepresentable, completion: (Result<T, E>) -> ()) { let error = E(response) completion(.Error(error)) errorHandler.handleError(error) } }
["response": { "items": [{"id":1, "text": "some"}, {"id":2, "text": "awesome"}], "count": 231 }]
struct WallItem: Decodable { var id: Int var text: String }
struct VKAPISuccessChecker: SuccessResponseChecker { let jsonSerializer = JSONSerializer() func isSuccessResponse(_ response: ResponseRepresentable) -> Bool { guard let httpResponse = response.response as? HTTPURLResponse else { return false } let isSuccesAccordingToStatusCode = Range(uncheckedBounds: (200, 300)).contains(httpResponse.statusCode) guard let data = response.data else { return false } guard let json = try? jsonSerializer.serialize(data) else { return false } return isSuccesAccordingToStatusCode && !json.keys.contains("error") } }
let service = BaseService<Wall, VKAPIError>() service.request = GETWallRequest() let responseHandler = HTTPResponseHandler<Wall, VKAPIError>() responseHandler.nestedModelGetter = ResponseModelGetter.wallResponse responseHandler.successResponseChecker = VKAPISuccessChecker() service.responseHandler = responseHandler return service
protocol ErrorRepresentable { var message: String? { get set } var errorCode: Int? { get set } var type: ErrorType { get set } init(_ type: ErrorType) init(_ response: ResponseRepresentable) } protocol ErrorType { var rawValue: String { get } }
enum VKAPIErrorType: String, ErrorType { case invalidAccessToken case unknownError } struct VKAPIError: ErrorRepresentable { var errorCode: Int? var message: String? var type: ErrorType = VKAPIErrorType.unknownError init(_ type: ErrorType) { self.type = type } init(_ response: ResponseRepresentable) { guard let data = response.data else { return } let jsonSerializer = JSONSerializer() guard let dataJSON = try? jsonSerializer.serialize(data), let errorJSON = dataJSON["error"] as? JSON else { return } errorCode = errorJSON["error_code"] as? Int message = errorJSON["error_msg"] as? String guard let code = errorCode else { return } switch code { case 5: type = VKAPIErrorType.invalidAccessToken default: type = VKAPIErrorType.unknownError } } }
protocol APIBuilder { associatedtype ErrorType: ErrorRepresentable func buildAPI<T: Decodable>(_ responseType: T.Type, request: HTTPRequestRepresentable?, decodingProcessor: ModelDecodingProcessor<T>?, nestedModelGetter: NestedModelGetter? ) -> BaseService<T, ErrorType> }
class VKAPIBuilder: APIBuilder { typealias ErrorType = VKAPIError func buildAPI<T: Decodable>(_ responseType: T.Type, request: HTTPRequestRepresentable? = nil, decodingProcessor: ModelDecodingProcessor<T>? = nil, nestedModelGetter: NestedModelGetter? = nil) -> BaseService<T, VKAPIError> { let service = BaseService<T, VKAPIError>() service.request = request let responseHandler = HTTPResponseHandler<T, VKAPIError>() responseHandler.nestedModelGetter = nestedModelGetter responseHandler.successResponseChecker = VKAPISuccessChecker() if let decodingProcessor = decodingProcessor { responseHandler.decodingProcessor = decodingProcessor } service.responseHandler = responseHandler return service } }
protocol WallGettable { func getWall(completion: @escaping (Wall) -> ()) } protocol WallPostable { func postWall(with message: String) } typealias WallAPI = WallGettable & WallPostable
class BasicWallAPI: WallAPI { private lazy var getWallService: BaseService<Wall, VKAPIError> = { return VKAPIBuilder().buildAPI(Wall.self, nestedModelGetter: ResponseModelGetter.wallResponse) }() private lazy var postService: BaseService<[String: [String: Int]], VKAPIError> = { return VKAPIBuilder().buildAPI([String: [String: Int]].self) }() func getWall(fromOwnerWith id: String, completion: @escaping (Wall) -> ()) { getWallService.request = WallRouter.GET(id, count: 20) getWallService.sendRequest()?.onSucces({ (wall) in completion(wall) }) } func postWall(with message: String) { postService.request = WallRouter.POST(message: message) postService.sendRequest() } }
struct WallRouter { struct GET: HTTPGETRequest { var path: String = "https://api.vk.com/method/wall.get" var parameters: JSON? = [:] var headerFields: [String: String]? init(_ ownerID: String, count: Int) { parameters?["owner_id"] = ownerID parameters?["count"] = count } } struct POST: HTTPPOSTRequest { var path: String = "https://api.vk.com/method/wall.post" var parameters: JSON? = [:] var headerFields: [String: String]? var bodyString: String? = nil init(message: String) { parameters?["message"] = message } } }
protocol WallGettable { func getWall(completion: @escaping (Wall) -> ()) func getWallItemsCount(completion: @escaping (Int) -> ()) }
private lazy var getWallItemsCountService: BaseService<Int, VKAPIError> = { return VKAPIBuilder().buildAPI(Int.self, decodingProcessor: IntDecodingProcessor(), nestedModelGetter: ResponseModelGetter.wallResponseCount) }() func getWallItemsCount(fromOwnerWith id: String, completion: @escaping (Int) -> ()) { getWallItemsCountService.request = WallRouter.GET(id, count: 1) getWallItemsCountService.sendRequest()?.onSucces({ (count) in completion(count) }) }
enum ResponseModelGetter: String, NestedModelGetter { case wallResponse = "response" case wallResponseItems = "response.items" case wallResponseCount = "response.count" case wallResponseFirstText = "response.items.text" var keyPath: String { return self.rawValue } }
override func viewDidLoad() { super.viewDidLoad() getWallAPI.getWallItemsCount(fromOwnerWith: "<some id>") { (count) in print("Wall items count is \(count)") } }
Wall items count is 1650
Source: https://habr.com/ru/post/349792/
All Articles