如何从 Swift 3 中的闭包中返回

Posted

技术标签:

【中文标题】如何从 Swift 3 中的闭包中返回【英文标题】:How to return from closures in Swift 3 【发布时间】:2017-08-03 14:15:11 【问题描述】:

我曾经在 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

我在下面的代码 sn-p 中调用这个函数。

[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()
    

任何帮助将不胜感激。

【问题讨论】:

completionHandler(nil, results)? 在 Swift 中,这将被视为完成处理程序(闭​​包)的输入参数。但是,在上面的代码中,我并没有在定义中提到闭包的任何输入参数。 糟糕,没有仔细阅读:@escaping () -> (Error?, [[String : Any]])) 为什么在() 那里没有任何转义?你的意思是completionHandler: @escaping(Error?,[[String : Any]]?) -> Void) -> Void ? (第一个 Void 用于闭包,第二个用于“方法”)。 @caffieneToCode 你会尝试我的回答吗 【参考方案1】:

您只需编写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)
 

【讨论】:

在 Swift 中,这将被视为完成处理程序(闭​​包)的输入参数。但是,在上面的代码中,我没有在定义中提到闭包的任何输入参数。 completionHandler: @escaping () -> (Error?, [[String : Any]] 这是大多数Swift完成处理程序的定义方式,包括例如URLSession.dataTask的完成处理程序,那么为什么不这样做呢? dataTask方法的定义有输入参数,但是这里我想把结果发送给调用方法。 (我仍然会尝试使用输入参数)打开 func dataTask(带有请求:URLRequest,completionHandler:@escaping(数据?,URLResponse?,错误?)-> Swift.Void)-> URLSessionDataTask 问题是,你想要达到的目标是不可能的。如果闭包是函数输入参数的一部分,则不能从闭包返回。你可以让函数返回一个闭包,但是如果你给它一个闭包作为输入参数,这个闭包就不能返回。见this answer【参考方案2】:

感谢您的帮助。

通过对闭包的更多研究,我得到了结果。

下面是我创建的 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()
    

我希望这可以帮助任何寻找类似解决方案的人。

【讨论】:

以上是关于如何从 Swift 3 中的闭包中返回的主要内容,如果未能解决你的问题,请参考以下文章

swift - 如何从系统函数的完成处理程序闭包中返回?

Swift - 我如何在闭包之外返回数组值?

如何从 Swift 的闭包中接收方法的输出?

Swift 中的尾随闭包语法是啥?

无论如何要从 Swift 中的 socket.io 方法返回一些东西吗?

无法从同一类中的闭包中更改变量 [swift 3.0]