I am trying to implement a retry mechanism and i saw that alamofire has one.
I am trying to implement a simple mechanism of retry with number of times for a request , yet something is wrong.
class OAuth2Handler: RequestAdapter, RequestRetrier {
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
return urlRequest
}
var defaultRetryCount = 4
private var requestsAndRetryCounts: [(Request, Int)] = []
private var lock = NSLock()
private func index(request: Request) -> Int? {
return requestsAndRetryCounts.index(where: { $0.0 === request })
}
func addRetryInfo(request: Request, retryCount: Int? = nil) {
lock.lock() ; defer { lock.unlock() }
guard index(request: request) == nil else { print("ERROR addRetryInfo called for already tracked request"); return }
requestsAndRetryCounts.append((request, retryCount ?? defaultRetryCount))
}
func deleteRetryInfo(request: Request) {
lock.lock() ; defer { lock.unlock() }
guard let index = index(request: request) else { print("ERROR deleteRetryInfo called for not tracked request"); return }
requestsAndRetryCounts.remove(at: index)
}
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion){
lock.lock() ; defer { lock.unlock() }
guard let index = index(request: request) else { completion(false, 0); return }
let (request, retryCount) = requestsAndRetryCounts[index]
if retryCount == 0 {
completion(false, 0)
} else {
requestsAndRetryCounts[index] = (request, retryCount - 1)
completion(true, 0.5)
}
}
}
this is the class that i am trying to use this:
let sessionManager = SessionManager()
override init() {
sessionManager.adapter = RequestAdapter.self as? RequestAdapter
sessionManager.retrier = OAuth2Handler()
}
func sendRequest(url: String,meth: HTTPMethod,parameters: [String: AnyObject]?, success: #escaping (String, Data) -> Void, failure: #escaping (Error) -> Void) {
self.asyncSerialWorker.enqueueWork { (done) in
self.sessionManager.request(url, method:meth).responseJSON { (responseObject) -> Void in
if responseObject.result.isSuccess {
print("Generic succsess")
let value = responseObject.result.value
let json = JSON(value!)
guard let result = responseObject.data else {return}
success(self.parser.parseMaiden(json: json), result)
}
if responseObject.result.isFailure {
let error : Error = responseObject.result.error!
print("login failed")
failure(error)
}
done()
}
}
}
if there are any other suggestions i would love to hear them
thanks
sessionManager.adapter = RequestAdapter.self as? RequestAdapter seems very wrong. You should be setting it to an instance of your OAuth2Handler.
So the issue her was to add the request to the retry, so first i did this:
let sessionManager = SessionManager()
var retrier = OAuth2Handler()
override init() {
sessionManager.retrier = retrier
}
and in the call itself i did as follow:
func sendRequest(url: String,meth: HTTPMethod,parameters: [String: AnyObject]?, success: #escaping (String, Data) -> Void, failure: #escaping (Error) -> Void) {
let request = sessionManager.request(url, method: meth, parameters: parameters, encoding: JSONEncoding.default)
retrier.addRetryInfo(request: request)
self.asyncSerialWorker.enqueueWork { (done) in
self.sessionManager.request(url, method:meth).responseJSON { (responseObject) -> Void in
if responseObject.result.isSuccess {
print("Generic succsess")
let value = responseObject.result.value
let json = JSON(value!)
guard let result = responseObject.data else {return}
success(self.parser.parseMaiden(json: json), result)
}
if responseObject.result.isFailure {
let error : Error = responseObject.result.error!
print("login failed")
failure(error)
}
done()
}
}
}
as you can see i have add to the retry a request :
retrier.addRetryInfo(request: request)
maybe i should do a remove in success(will check and update)
Related
I'm currently trying to find out what's the best networking architecture for MVVM applications. I couldn't find many resources and decided to go with dependency injection based architecture as per the very less reading resources that I have found.
I'm not using any 3rd party for web service testing and whatever the networking architecture that I use should be supported to mock the web services as well.
I have found out the DI based networking architecture which was build intended to achieve unit testing according to the Test Pyramid concept at Apple WWDC 2018.
So I have build my networking layer according to that. Following is my APIHandler class.
public enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
}
protocol RequestHandler {
associatedtype RequestDataType
func makeRequest(from data:RequestDataType) -> URLRequest?
}
protocol ResponseHandler {
associatedtype ResponseDataType
func parseResponse(data: Data, response: HTTPURLResponse) throws -> ResponseDataType
}
typealias APIHandler = RequestHandler & ResponseHandler
Followings are my extensions for request handler and response handler.
extension RequestHandler {
func setQueryParams(parameters:[String: Any], url: URL) -> URL {
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
components?.queryItems = parameters.map { element in URLQueryItem(name: element.key, value: String(describing: element.value) ) }
return components?.url ?? url
}
func setDefaultHeaders(request: inout URLRequest) {
request.setValue(APIHeaders.contentTypeValue, forHTTPHeaderField: APIHeaders.kContentType)
}
}
struct ServiceError: Error,Decodable {
let httpStatus: Int
let message: String
}
extension ResponseHandler {
func defaultParseResponse<T: Decodable>(data: Data, response: HTTPURLResponse) throws -> T {
let jsonDecoder = JSONDecoder()
if response.statusCode == 200 {
do {
let body = try jsonDecoder.decode(T.self, from: data)
return body
} catch {
throw ServiceError(httpStatus: response.statusCode, message: error.localizedDescription)
}
} else {
var message = "Generel.Message.Error".localized()
do {
let body = try jsonDecoder.decode(APIError.self, from: data)
if let err = body.fault?.faultstring {
message = err
}
} catch {
throw ServiceError(httpStatus: response.statusCode, message: error.localizedDescription)
}
throw ServiceError(httpStatus: response.statusCode, message:message)
}
}
}
Then I loaded my request using APILoader as follows.
struct APILoader<T: APIHandler> {
var apiHandler: T
var urlSession: URLSession
init(apiHandler: T, urlSession: URLSession = .shared) {
self.apiHandler = apiHandler
self.urlSession = urlSession
}
func loadAPIRequest(requestData: T.RequestDataType, completionHandler: #escaping (Int, T.ResponseDataType?, ServiceError?) -> ()) {
if let urlRequest = apiHandler.makeRequest(from: requestData) {
urlSession.dataTask(with: urlRequest) { (data, response, error) in
if let httpResponse = response as? HTTPURLResponse {
guard error == nil else {
completionHandler(httpResponse.statusCode, nil, ServiceError(httpStatus: httpResponse.statusCode, message: error?.localizedDescription ?? "General.Error.Unknown".localized()))
return
}
guard let responseData = data else {
completionHandler(httpResponse.statusCode,nil, ServiceError(httpStatus: httpResponse.statusCode, message: error?.localizedDescription ?? "General.Error.Unknown".localized()))
return
}
do {
let parsedResponse = try self.apiHandler.parseResponse(data: responseData, response: httpResponse)
completionHandler(httpResponse.statusCode, parsedResponse, nil)
} catch {
completionHandler(httpResponse.statusCode, nil, ServiceError(httpStatus: httpResponse.statusCode, message: CommonUtil.shared.decodeError(err: error)))
}
} else {
guard error == nil else {
completionHandler(-1, nil, ServiceError(httpStatus: -1, message: error?.localizedDescription ?? "General.Error.Unknown".localized()))
return
}
completionHandler(-1, nil, ServiceError(httpStatus: -1, message: "General.Error.Unknown".localized()))
}
}.resume()
}
}
}
To call my API request. I have created a separate service class and call the web service as follows.
struct TopStoriesAPI: APIHandler {
func makeRequest(from param: [String: Any]) -> URLRequest? {
let urlString = APIPath().topStories
if var url = URL(string: urlString) {
if param.count > 0 {
url = setQueryParams(parameters: param, url: url)
}
var urlRequest = URLRequest(url: url)
setDefaultHeaders(request: &urlRequest)
urlRequest.httpMethod = HTTPMethod.get.rawValue
return urlRequest
}
return nil
}
func parseResponse(data: Data, response: HTTPURLResponse) throws -> StoriesResponse {
return try defaultParseResponse(data: data,response: response)
}
}
For syncing both my actual web service methods and mock services, I have created an API Client protocol like follows.
protocol APIClientProtocol {
func fetchTopStories(completion: #escaping (StoriesResponse?, ServiceError?) -> ())
}
Then I have derived APIServices class using my APIClient protocol and implemented my all the APIs there by passing requests and responses. My dependency injection was getting over at this point.
public class APIServices: APIClientProtocol {
func fetchTopStories(completion: #escaping (StoriesResponse?, ServiceError?) -> ()) {
let request = TopStoriesAPI()
let params = [Params.kApiKey.rawValue : CommonUtil.shared.NytApiKey()]
let apiLoader = APILoader(apiHandler: request)
apiLoader.loadAPIRequest(requestData: params) { (status, model, error) in
if let _ = error {
completion(nil, error)
} else {
completion(model, nil)
}
}
}
}
Then I have called this API request on my viewModel class like this.
func fetchTopStories(completion: #escaping (Bool) -> ()) {
APIServices().fetchTopStories { response, error in
if let _ = error {
self.errorMsg = error?.message ?? "Generel.Message.Error".localized()
completion(false)
} else {
if let data = response?.results {
if data.count > 0 {
self.stories.removeAll()
self.stories = data
completion(true)
} else {
self.errorMsg = "Generel.NoData.Error".localized()
completion(false)
}
} else {
self.errorMsg = "Generel.NoData.Error".localized()
completion(false)
}
}
}
}
Finally call the viewModel's API call from my viewController (View).
func fetchData() {
showActivityIndicator()
self.viewModel.fetchTopStories { success in
self.hideActivityIndicator()
DispatchQueue.main.async {
if self.pullToRefresh {
self.pullToRefresh = false
self.refreshControl.endRefreshing()
}
if success {
if self.imgNoData != nil {
self.imgNoData?.isHidden = true
}
self.tableView.reloadData()
} else {
CommonUtil.shared.showToast(message: self.viewModel.errorMsg, success: false)
self.imgNoData = {
let viewWidth = self.tableView.frame.size.width
let imageWidth = viewWidth - 50
let iv = UIImageView()
iv.frame = CGRect(x: 25, y: 100, width: imageWidth, height: imageWidth)
iv.image = UIImage(named:"no-data")
iv.contentMode = .scaleAspectFit
return iv
}()
self.imgNoData?.isHidden = false
self.tableView.addSubview(self.imgNoData!)
}
}
}
}
So I have following questions regarding this approach.
I have ended the dependency injection from my APIServices class.
Should I bring this all the way up to my viewController class API Call and
pass request and params variables from there ?
Are there any performance issues in this approach and any
improvement to be done?
My personal preference is to end all the data related stuffs from the viewModel level and just call the API without passing any parameters from the viewController. Does it wrong? If we pass parameters from the view controller class as per the pure dependency injection way, does it harm to the MVVM architecture?
Here I have 3 files loginView(SwiftUI file) for UI purpose, LoginViewModel for handling the logic, ServiceManager for handling the Network call
Below code is in loginView(SwiftUI file)
Button("Login") {
loginVM.loginWebserviceCall()
}
Below code is in loginVM class
protocol LoginViewModelService: AnyObject {
func getLoginWebServiceCall(url: URL, params: [String: Any], completion: #escaping (Result<LoginRequest, APIError>) -> ())
}
class LoginViewModel: ObservableObject {
private weak var movieService: LoginViewModelService?
#Published var error: NSError?
init(movieService: LoginViewModelService = LoginStore.shared) {
self.movieService = movieService
}
fileprivate func loginWebserviceCall() {
let loginParams = ["username": "userEnteredUserName", "password": "userEnteredPassword", "request_token": "token"]
self.movieService!.getLoginWebServiceCall(url: "API_URL",
params: loginParams) { [weak self] (result) in
guard let self = self else { return }
switch result {
case .success(let response):
print(response)
case .failure(let error):
self.error = error as NSError
}
}
}
}
class LoginStore: LoginViewModelService {
static let shared = LoginStore()
private init() {}
func getLoginWebServiceCall(url: URL, params: [String: Any], completion: #escaping (Result<LoginRequest, APIError>) -> ()) {
ServiceManager.shared().requestWebServiceCall(requestType: .POST, url: url, params: params, completion: completion)
}
}
Below code is in ServiceManager class
class ServiceManager: NSObject {
private static var manager: ServiceManager? = nil
static func shared() -> ServiceManager {
if manager == nil {
manager = ServiceManager()
}
return manager!
}
func requestWebServiceCall<Response: Decodable>(requestType: HTTPMethod,
url: URL, params: [String: Any]? = nil,
completion: #escaping (Result<Response, APIError>) -> ()) {
var urlRequest = URLRequest.init(url: url)
if let params = params {
let postData = try? JSONSerialization.data(withJSONObject: params, options: .init(rawValue: 0))
urlRequest.httpBody = postData
}
urlRequest.httpMethod = requestType.rawValue
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: urlRequest) { [weak self] (data, response, error) in
guard let self = self else { return }
guard let data = data else {
self.executeCompletionHandlerInMainThread(with: .failure(.noData), completion: completion)
return
}
do {
if let str = String(data: data, encoding: .utf8) {
print(str)
// Output: {"success":true,"expires_at":"2022-06-23 08:56:52 UTC","request_token":"6587563498567begjhgf3r5387853"}
}
let decodedResponse = try JSONDecoder().decode(Response.self, from: data)
self.executeCompletionHandlerInMainThread(with: .success(decodedResponse), completion: completion)
} catch let DecodingError.keyNotFound(key, context) {
print("Key '\(key)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch {
print(error)
}
}.resume()
}
private func executeCompletionHandlerInMainThread<Response: Decodable>(with result: Result<Response, APIError>,
completion: #escaping (Result<Response, APIError>) -> ()) {
DispatchQueue.main.async {
completion(result)
}
}
}
Below is the JSON we are expecting as response
{
"success": true,
"expires_at": "2018-07-24 04:10:26 UTC",
"request_token": "1531f1a558c8357ce8990cf887ff196e8f5402ec"
}
But once I get response, the decoding is getting failed and it is going inside catch block(in ServiceManager class) and it print's below error.
Key 'CodingKeys(stringValue: "username", intValue: nil)' not found: No value associated with key CodingKeys(stringValue: "username", intValue: nil) ("username").
codingPath: []
It is showing username as not found. But in my API response, I don't have username at all.
But I am passing username as httpBody to this API.
What could be the reason? Why it is throwing error?
So, I have this function which is copied from this GitHub gist.
protocol ClientProtocol {
func request<Response: Codable>(_ endpoint: Endpoint<Response>)-> Single<Response>
}
final class Client: ClientProtocol {
private let manager: Alamofire.Session
private let baseURL = URL(string: "http://192.168.20:8080")!
private let queue = DispatchQueue(label: "<your_queue_label>")
init(accessToken: String) {
let configuration = URLSessionConfiguration.default
configuration.headers.add(name: "Authorization", value: "Bearer \(accessToken)")
configuration.timeoutIntervalForRequest = 15
self.manager = Alamofire.Session.init(configuration: configuration)
//self.manager.retrier = OAuth2Retrier()
}
func request<Response>(_ endpoint: Endpoint<Response>) -> Single<Response> {
return Single<Response>.create { observer in
let request = self.manager.request(
self.url(path: endpoint.path),
method: httpMethod(from: endpoint.method),
parameters: endpoint.parameters
)
request
.validate()
.responseData(queue: self.queue) { response in
let result: Result<Response, AFError> = response.result.flatMap(endpoint.decode)
switch result {
case let .success(val): observer(.success(val))
case let .failure(err): observer(.error(err))
}
}
return Disposables.create {
request.cancel()
}
}
}
private func url(path: Path) -> URL {
return baseURL.appendingPathComponent(path)
}
}
private func httpMethod(from method: Method) -> Alamofire.HTTPMethod {
switch method {
case .get: return .get
case .post: return .post
case .put: return .put
case .patch: return .patch
case .delete: return .delete
}
}
private class OAuth2Retrier: Alamofire.RequestRetrier {
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: #escaping (RetryResult) -> Void) {
if (error as? AFError)?.responseCode == 401 {
// TODO: implement your Auth2 refresh flow
// See https://github.com/Alamofire/Alamofire#adapting-and-retrying-requests
}
// completion(false, 0)
}
}
Endpoint
// MARK: Defines
typealias Parameters = [String: Any]
typealias Path = String
enum Method {
case get, post, put, patch, delete
}
// MARK: Endpoint
final class Endpoint<Response> {
let method: Method
let path: Path
let parameters: Parameters?
let decode: (Data) throws -> Response
init(method: Method = .get, path: Path, parameters: Parameters? = nil, decode: #escaping (Data) throws -> Response) {
self.method = method
self.path = path
self.parameters = parameters
self.decode = decode
}
}
// MARK: Convenience
extension Endpoint where Response: Swift.Decodable {
convenience init(method: Method = .get, path: Path, parameters: Parameters? = nil) {
self.init(method: method, path: path, parameters: parameters) {
try JSONDecoder().decode(Response.self, from: $0)
}
}
}
extension Endpoint where Response == Void {
convenience init(method: Method = .get, path: Path, parameters: Parameters? = nil) {
self.init(method: method, path: path, parameters: parameters, decode: { _ in () })
}
}
At let result = response.result.flatMap(endpoint.decode) xCode is throwing
Type of expression is ambiguous without more context
The type of the response.result is Result<Data, AFError>
I want to flatmap it to Result< Response, AFError>.
I tried
let result = response.result.flatMap { (data) -> Result<Response, AFError> in
// don't know what to put here
}
flatMap(_:) doesn't take throwing closure and in EndPoint, decode is a throwing closure :
let decode: (Data) throws -> Response
try catching the error:
func request<Response>(_ endpoint: Endpoint<Response>) -> Single<Response> {
return Single<Response>.create { observer in
let request = self.manager.request(
self.url(path: endpoint.path),
method: httpMethod(from: endpoint.method),
parameters: endpoint.parameters
)
request
.validate()
.responseData(queue: self.queue) { response in
let result: Result<Response, AFError> = response.result.flatMap {
do {
return Result<Response, AFError>.success(try endpoint.decode($0))
} catch let error as AFError {
return Result<Response, AFError>.failure(error)
} catch {
fatalError(error.localizedDescription)
}
}
switch result {
case let .success(val): observer(.success(val))
case let .failure(err): observer(.failure(err))
}
}
return Disposables.create {
request.cancel()
}
}
}
Note
Since you seem only interested in AFError this code will call fatalError this may not a good idea though.
You are using flatMap when you should be using map...
func example(response: Response, endpoint: Endpoint<Thing>) {
let result = response.result.map(endpoint.decode)
}
struct Response {
let result: Result<Data, Error>
}
struct Endpoint<T> {
func decode(_ data: Data) -> T { fatalError() }
}
struct Thing { }
I'm using RequestRetrier to automatically renew access_token for my API. But in each request function I would like to catch timeout error, but .case(let error) in .responseJSON body never executes, because of (I guess) retrier that I add to my accessSessionManager. Here's how it looks like:
lazy var accessSessionManager: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = Configuration.timeout
configuration.timeoutIntervalForResource = Configuration.timeout
let sessionManager = Alamofire.SessionManager(configuration: configuration)
let oAuth2Handler = OAuth2Handler()
sessionManager.retrier = oAuth2Handler
sessionManager.adapter = oAuth2Handler
return sessionManager
}()
func changeName(newName: String,completionHandler: ((_ success: Bool, _ error: String?) -> ())?) {
guard let accessToken = self.getAccessToken() else { return }
let parameters = ["access_token": accessToken, "name": newName] as [String : Any]
self.accessSessionManager.request(Constants.nameUrl, method: .get, parameters: parameters).responseJSON { response in
switch response.result {
case .success(let json):
let jsonDict = JSON(json)
if let success = jsonDict["success"].bool {
completionHandler?(success, nil)
}
case .failure(let error):
if error._code == NSURLErrorTimedOut {
completionHandler?(false, "Please check your Internet connection and try again!")
} else if response.response?.statusCode == 400 {
completionHandler?(false, "Sorry, name not found")
} else if response.response?.statusCode != 401 {
completionHandler?(false, error.localizedDescription)
}
}
}
}
}
....
....
class OAuth2Handler {
//MARK: - Adapter
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
if let url = urlRequest.url {
guard let accessToken = self.getAccessToken() else { return urlRequest }
let newUrl = addOrUpdateQueryStringParameter(url: "\(url)", key: "access_token", value: accessToken)
let newRequest = URLRequest(url: URL(string: newUrl)!)
return newRequest
}
return urlRequest
}
// MARK: - RequestRetrier
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion) {
lock.lock() ; defer { lock.unlock() }
if let response = request.task?.response as? HTTPURLResponse {
if response.statusCode == 401 {
requestsToRetry.append(completion)
if !isRefreshing {
refreshTokens { [weak self] succeeded, accessToken, refreshToken in
guard let strongSelf = self else { return }
strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }
if let accessToken = accessToken, let refreshToken = refreshToken {
strongSelf.accessToken = accessToken
strongSelf.refreshToken = refreshToken
strongSelf.updateAccessToken(accessToken: accessToken, refreshToken: refreshToken)
}
strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
strongSelf.requestsToRetry.removeAll()
}
}
} else {
completion(false, 0.0)
}
}
}
So, basically error handling performs in should function, not in .case(let error) in my function.
Ok, so there's my very stupid mistake, basically completion(false,0,0) was never executed if error occurred in should function. Everything works if it looks something like this:
// MARK: - RequestRetrier
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion) {
lock.lock() ; defer { lock.unlock() }
if let response = request.task?.response as? HTTPURLResponse {
if response.statusCode == 401 {
requestsToRetry.append(completion)
if !isRefreshing {
refreshTokens { [weak self] succeeded, accessToken, refreshToken in
guard let strongSelf = self else { return }
strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }
if let accessToken = accessToken, let refreshToken = refreshToken {
strongSelf.accessToken = accessToken
strongSelf.refreshToken = refreshToken
strongSelf.updateAccessToken(accessToken: accessToken, refreshToken: refreshToken)
}
strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
strongSelf.requestsToRetry.removeAll()
}
}
} else {
completion(false, 0.0)
} else {
completion(false,0.0)
}
}
You are not validating your request. Therefor it will let every request be a succes. Try validating your request by adding a validate() after the request but before the response:
self.accessSessionManager.request(...).validate().responseJSON { ... }
You can find custom ways to change the behavior what is and what is not accepted by the validate() function in their readme
I'm having trouble with the ResponseSerializer I get an unresolved identifier and for Response I get an undeclared type. I've read from alamofire migration doc that Response has been changed to multiple types. So I should change Response->DataReponse but this means I can only pass one argument like:
// What I have
Response(<ListWrapper, NSError>)
// What I should change it to?
DataResponse(<ListWrapper>)
How can I still recieve the Error this way and more importantly how do I migrate the extension to alamofire 4?
My class:
class List{
var idNumber: String?
var title: String?
var posterPath: String?
var release: String?
required init(json: JSON, id: Int?)
{
self.idNumber = json[ListFields.Id.rawValue].stringValue
self.title = json[ListFields.Title.rawValue].stringValue
self.posterPath = json[ListFields.PosterPath.rawValue].stringValue
self.release = json[ListFields.Release.rawValue].stringValue
}
class func setURL_APPEND(_ url: String)
{
URL_APPEND = url
}
// MARK: Endpoints
class func endpointForList() -> String
{
return URL_APPEND
}
fileprivate class func getListAtPath(_ path: String, completionHandler: #escaping (ListWrapper?, NSError?) -> Void) {
Alamofire.request(path)
.responseListArray { response in
if let error = response.result.error
{
completionHandler(nil, error)
return
}
completionHandler(response.result.value, nil)
}
}
class func getList(_ completionHandler: #escaping (ListWrapper?, NSError?) -> Void)
{
getListAtPath(List.endpointForList(), completionHandler: completionHandler)
}
}
// Problem is here:
// for ResponseSerializer I get an unresolved identifier
// and for Response I get an undeclared type
extension Alamofire.Request {
func responseListArray(_ completionHandler: #escaping (Response<ListWrapper, NSError>) -> Void) -> Self {
let responseSerializer = ResponseSerializer<ListWrapper, NSError> { request, response, data, error in
guard error == nil else
{
return .failure(error!)
}
guard let responseData = data else {
let failureReason = "Array could not be serialized because input data was nil."
let error = Alamofire.Error.errorWithCode(.dataSerializationFailed, failureReason: failureReason)
return .failure(error)
}
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .allowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
switch result {
case .success(let value):
let json = SwiftyJSON3.JSON(value)
let wrapper = ListWrapper()
var allList:Array = Array<List>()
wrapper.totalCount = json["favorite_count"].intValue
// print(json)
let results = json["items"]
// print(results)
for jsonList in results
{
//print(jsonList.1)
let list = List(json: jsonList.1, id: Int(jsonList.0) )
if (list.posterPath == "")
{
continue
}
else
{
//print(movies.posterPath)
allList.append(list)
}
}
wrapper.results = allList
return .success(wrapper)
case .failure(let error):
return .failure(error)
}
}
return response(responseSerializer: responseSerializer,completionHandler: completionHandler)
}
}
Bro try below code see:
func responseListArray(_ completionHandler: #escaping (Response<ListWrapper>) -> Void) -> Self {
let responseSerializer = ResponseSerializer<ListWrapper> { request, response, data, error in
guard error == nil else
{
return .failure(error!)
}
guard let responseData = data else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNil))
}
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .allowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
switch result {
case .success(let value):
let json = SwiftyJSON3.JSON(value)
let wrapper = ListWrapper()
var allList:Array = Array<List>()
wrapper.totalCount = json["favorite_count"].intValue
// print(json)
let results = json["items"]
// print(results)
for jsonList in results
{
//print(jsonList.1)
let list = List(json: jsonList.1, id: Int(jsonList.0) )
if (list.posterPath == "")
{
continue
}
else
{
//print(movies.posterPath)
allList.append(list)
}
}
wrapper.results = allList
return .success(wrapper)
case .failure(let error):
return .failure(error)
}
}
return response(responseSerializer: responseSerializer,completionHandler: completionHandler)
}