Как вернуться из замыканий в Swift 3

Раньше я создавал одноэлементный класс в Objective-C для выполнения всех вызовов службы в моем коде. Однако в Swift используются замыкания, и я не могу добиться таких же результатов в Swift. Есть ли способ сделать то же самое в Swift 3?

@implementation ServiceManager

+(id)sharedManager {
      static ServiceManager *sharedMyManager = nil;
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
          sharedMyManager = [[self alloc] init];
      });
      return sharedMyManager;
}

-(NSURLSession *)urlSession {
if (_urlSession) {
    return _urlSession;
}

NSURLSessionConfiguration *defaultConfigObject =
 [NSURLSessionConfiguration defaultSessionConfiguration];
_urlSession = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
_urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
_urlSession.sessionDescription = @"net.socialInvesting.nsurlsession";

return _urlSession;
}

-(void)ExecuteRequest:(NSURLRequest *)request withCompletion:(void (^)(id result, NSError *error))completionBlock {

NSURLSessionDataTask * dataTask =[[self urlSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if(error == nil) {
        id result = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
        completionBlock(result,error);
    } else {
        NSLog(@"Error: %@", error.localizedDescription);
        completionBlock(nil, error);
    }
}];
[dataTask resume];
}

@end

Я вызываю эту функцию, как показано во фрагменте кода ниже.

[sharedServiceManager ExecuteRequest:urlRequest withCompletion:^(id result, NSError *error) {
    if (result) {
        LoginModel *model = [[LoginModel alloc]initWithDictionary:result error:NULL];
        ResponseManager *manager = [ResponseManager sharedManager];
        manager.loginResponse = model;
        completionBlock(YES,NULL);
    } else {
        completionBlock(NO,error);
    }
}];

Вот как я пытался сделать подобное выполнение в Swift. Но не может вернуть значения.

import UIKit

class ServiceManager: UIView {

var session = URLSession.shared
static let sharedSessionManager = ServiceManager()

class func sharedManager() -> ServiceManager {
    return sharedSessionManager
}

func executeGetRequest(with urlString: String, inputDictionary:[String : Any], completionHandler: @escaping () -> (Error?, [[String : Any]])) {
    let url = URL.init(string: urlString)
    let urlRequest = URLRequest(url: url!)
    let task = session.dataTask(with: urlRequest) { (data, response, error) in
        if error != nil {
            print("ERROR: could not execute request")
        } else {
            do {
                let responseDict = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
                if let results = responseDict!["results"] as? [[String:Any]] {
                    completionHandler // How to return results and Error from here to the calling function
                }
            } catch {
                    print("ERROR: could not retrieve response")
                }
        }
    }
    task.resume()
    }
}

Любая помощь будет оценена по достоинству.


person caffieneToCode    schedule 03.08.2017    source источник
comment
В Swift это будет рассматриваться как входные параметры для завершенияHandler (закрытие). Однако в приведенном выше коде я не упомянул какие-либо входные параметры для замыкания в определении.   -  person caffieneToCode    schedule 03.08.2017
comment
Упс, невнимательно прочитал: @escaping () -> (Error?, [[String : Any]])) Почему он там () без ничего для побега? Вы имели в виду completionHandler: @escaping(Error?,[[String : Any]]?) -> Void) -> Void {? (первый Void для замыкания, второй для метода).   -  person Larme    schedule 03.08.2017
comment
@caffieneToCode не могли бы вы попробовать мой ответ   -  person Nazmul Hasan    schedule 03.08.2017


Ответы (2)


Вам просто нужно написать completionHandler(arg1, arg2,...) и поместить значения, которые вы хотите вернуть, в круглые скобки, как при вызове функции. Вы должны изменить обработчик завершения, чтобы он возвращал необязательные значения для обоих своих аргументов, поскольку, если вы получаете ошибку, лучше вернуть nil, чем пустой словарь.

do {
    let responseDict = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
    if let results = responseDict!["results"] as? [[String:Any]] {
        completionHandler(nil, responseDict)
    }
 } catch {
     completionHandler(error, nil)
 }
person Dávid Pásztor    schedule 03.08.2017
comment
В Swift это будет рассматриваться как входные параметры для завершенияHandler (закрытие). Однако в приведенном выше коде я не упомянул какие-либо входные параметры для замыкания в определении. завершениеHandler: @escaping () -> (Ошибка?, [[String : Any]] - person caffieneToCode; 03.08.2017
comment
Именно так определяется большинство обработчиков завершения Swift, включая, например, обработчик завершения URLSession.dataTask, так почему бы не сделать это таким же образом? - person Dávid Pásztor; 03.08.2017
comment
Определение метода dataTask имеет входные параметры, но здесь я хочу отправить результат вызывающему методу. (все же я бы попробовал с входными параметрами) open func dataTask (с запросом: URLRequest, completeHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> URLSessionDataTask - person caffieneToCode; 03.08.2017
comment
Проблема в том, что то, чего вы хотите достичь, невозможно. Вы не можете вернуться из замыкания, если это замыкание является частью входных аргументов вашей функции. Вы можете заставить функцию возвращать замыкание, но если вы предоставите ей замыкание в качестве входного аргумента, это замыкание не сможет вернуться. См. этот ответ - person Dávid Pásztor; 03.08.2017

Спасибо за помощь.

Проведя еще несколько исследований замыканий, я добился результата.

Ниже приведен созданный мною Singleton ServiceManager.

class ServiceManager: NSObject {

    //  Static Instance variable for Singleton
    static var sharedSessionManager = ServiceManager()

    //  Preventing initialisation from any other source.
    private init() {
    }

    //  Function to execute GET request and pass data from escaping closure
    func executeGetRequest(with urlString: String, completion: @escaping (Data?) -> ()) {

        let url = URL.init(string: urlString)
        let urlRequest = URLRequest(url: url!)

        URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
        //  Log errors (if any)
            if error != nil {
                print(error.debugDescription)
            } else {
                //  Passing the data from closure to the calling method
                completion(data)
            }
        }.resume()  // Starting the dataTask
    }

    //  Function to perform a task - Calls executeGetRequest(with urlString:) and receives data from the closure.
    func downloadMovies(from urlString: String, completion: @escaping ([Movie]) -> ()) {
        //  Calling executeGetRequest(with:)
        executeGetRequest(with: urlString) { (data) in  // Data received from closure
            do {
                //  JSON parsing
                let responseDict = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
                if let results = responseDict!["results"] as? [[String:Any]] {
                    var movies = [Movie]()
                    for obj in results {
                        let movie = Movie(movieDict: obj)
                        movies.append(movie)
                    }
                //  Passing parsed JSON data from closure to the calling method.
                    completion(movies)
                }
            } catch {
                print("ERROR: could not retrieve response")
            }
        }
    }
}

Пример использования ниже.

ServiceManager.sharedSessionManager.downloadMovies(from: urlBase) { (movies : [Movie]) in   // Object received from closure
    self.movies = movies
    DispatchQueue.main.async {
         //  Updating UI on main queue
        self.movieCollectionView.reloadData()
    }
}

Я надеюсь, что это поможет любому, кто ищет подобное решение.

person caffieneToCode    schedule 09.08.2017