Обработчики завершения в Swift 4

У меня проблема, которую я пытаюсь осмыслить, связанная с использованием обработчиков завершения. У меня есть 3 слоя в моей программе iOS: ViewController-> Service-> Networking. Мне нужно загрузить некоторые данные через вызов API из контроллера представления.

Я определил функции (завершенияHandlers) в ViewController, которые должны выполняться после завершения запроса данных, и мне комфортно при реализации обработчиков завершения, когда существует только два уровня, но сбивает с толку, когда в следующем сценарии:

DashboardViewController.swift

import UIKit

@IBDesignable class DashboardViewController: UIViewController {

    @IBOutlet weak var stepCountController: ExpandedCardView!
    var articles:[Article]?
    let requestHandler = RequestHandler()
    let dashboardService = DashboardService()

    override func viewDidLoad() {
        super.viewDidLoad()
        dashboardService.getDashboardData(completionHandler: getDashboardDataCompletionHandler)
    }

    func getDashboardDataCompletionHandler(withData: DashboardDataRequest) {
        print(withData)
    }
} 

DashboardService.swift

import Foundation

class DashboardService: GeneralService {

    var requestHandler: DashboardRequestHandler

    override init() {
        requestHandler = DashboardRequestHandler()
        super.init()
    }

    //this function should execute requestHandler.requestDashboardData(), and then execute convertDashboardData() with the result of previous get request
    func getDashboardData(completionHandler: @escaping (DashboardDataRequest) -> Void) {
        //on network call return
        guard let url = URL(string: apiResourceList?.value(forKey: "GetDashboard") as! String) else { return }
        requestHandler.requestDashboardData(url: url, completionHandler: convertDashboardData(completionHandler: completionHandler))
    }

    func convertDashboardData(completionHandler: (DashboardDataRequest) -> Void) {
        //convert object to format acceptable by view
    }
}

DashboardRequestHandler.swift

import Foundation

class DashboardRequestHandler: RequestHandler {

    var dataTask: URLSessionDataTask?

    func requestDashboardData(url: URL, completionHandler: @escaping (DashboardDataRequest) -> Void) {
        dataTask?.cancel()

        defaultSession.dataTask(with: url, completionHandler: {(data, response, error) in
            if error != nil {
                print(error!.localizedDescription)
            }

            guard let data = data else {
                return
            }

            do {
                let decodedJson = try JSONDecoder().decode(DashboardDataRequest.self, from: data)
                completionHandler(decodedJson)
            } catch let jsonError {
                print(jsonError)
            }

        }).resume()
    }
}

Если вы посмотрите комментарий в DashboardService.swift, моя проблема очевидна. Я передаю обработчик завершения из ViewController в службу, и у службы есть собственный обработчик завершения, который он передает в RequestHandler, где обработчик завершения контроллера представления (getDashboardDataCompletionHandler) должен выполняться после обработчика завершения службы (convertDashboardData ())

Пожалуйста, помогите мне прояснить, как это реализовать. Допускаю ли я ошибку в дизайне, пытаясь связать такие обработчики завершения, или я упускаю что-то очевидное.

Спасибо

- РЕДАКТИРОВАТЬ - Моя реализация обработчика запросов выглядит следующим образом:

import Foundation

class RequestHandler {
    //    let defaultSession = URLSession(configuration: .default)
    var defaultSession: URLSession!

    init() {
        guard let path = Bundle.main.path(forResource: "Api", ofType: "plist") else {
            print("Api.plist not found")
            return
        }
        let apiResourceList = NSDictionary(contentsOfFile: path)
        let config = URLSessionConfiguration.default
        if let authToken = apiResourceList?.value(forKey: "AuthToken") {
            config.httpAdditionalHeaders = ["Authorization": authToken]
        }
        defaultSession = URLSession(configuration: config)
    }
}

person jms    schedule 12.03.2018    source источник
comment
Не могли бы вы дать свое определение типа RequestHandler?   -  person user3581248    schedule 12.03.2018
comment
сделано. это просто инициализирует мой URLSession с необходимыми заголовками для связи с моим сервером   -  person jms    schedule 12.03.2018
comment
Код, который у вас есть, должен работать, если вы переключите строку convertDashboardData(completionHandler: completionHandler) на completionHandler в DashboardService.swift   -  person user3581248    schedule 12.03.2018
comment
И по поводу этого комментария: //convert object to format acceptable by view - если вы хотите этого добиться, ваш DashboardService также должен выполнять роль адаптера, поэтому объявление метода getDashboardData должно выглядеть так: func getDashboardData(completionHandler: @escaping (SomeTypeThatAdjustsDashboardDataToFormatAcceptableByView) -> Void). И тогда вам нужно будет выполнить это преобразование в DashboardService   -  person user3581248    schedule 12.03.2018
comment
где SomeTypeThatAdjustsDashboardDataToFormatAcceptableByView будет другой функцией / закрытием, не так ли?   -  person jms    schedule 12.03.2018
comment
SomeTypeThatAdjustsDashboardDataToFormatAcceptableByView - это тип; (SomeTypeThatAdjustsDashboardDataToFormatAcceptableByView) -> Void будет закрытием   -  person user3581248    schedule 12.03.2018


Ответы (1)


В этом случае более понятно использовать делегирование, например так:

protocol DashboardServiceDelegate {
    func didLoaded(_ viewModel: DashboardViewModel)
}

class DashboardViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        dashboardService.delegate = self
        dashboardService.getDashboardData()
    }
}
extension DashboardViewController: DashboardServiceDelegate {
    func didLoaded(_ viewModel: DashboardViewModel) {
        ///
    }
}


protocol DashboardRequestHandlerDelegate() {
    func didLoaded(request: DashboardDataRequest)
}

class DashboardService: GeneralService {
    private lazy var requestHandler: DashboardRequestHandler = { reqHandler in
        reqHandler.delegate = self
        return reqHandler
    }(DashboardRequestHandler())

    func getDashboardData() {
        guard let url = ...
        requestHandler.requestDashboardData(url: url)
    }
}

extension DashboardService: DashboardRequestHandlerDelegate {
    func didLoaded(request: DashboardDataRequest) {
        let viewModel = convert(request)
        delegate.didLoaded(viewModel)
    }
}
person Andrey Volobuev    schedule 12.03.2018
comment
Я бы предположил, что в реализации requestHandler.requestDashboardData (url: URL) мне пришлось бы вызвать delegate.didLoaded (request) ?? - person jms; 13.03.2018
comment
Да, вам нужно будет вызвать его после декодирования из json - person Andrey Volobuev; 13.03.2018
comment
Извините, я так долго принимал ваш ответ. Я как бы реализовал метод завершенияHandler, когда вы опубликовали этот ответ. Теперь я понимаю, что считаю метод делегата более чистым в этом сценарии и реализовал его «Я - мой код». Спасибо - person jms; 13.03.2018