【问题标题】:Swift: Asynchronous callbackSwift:异步回调
【发布时间】:2014-07-31 09:25:11
【问题描述】:

如何快速进行异步回调?我正在为我的应用程序编写一个小框架,因为它应该在 iOS 和 OS X 两者上运行。所以我将非设备特定的主要代码放入这个框架中,该框架还处理对我的在线 api 的请求。显然,我还希望应用程序的 GUI 以及我的 ViewControllers 在 api 请求完成后立即做出反应。在 Objective-C 中,我通过将包含必须在 id 变量中调用的函数的视图和函数本身保存在选择器变量中来做到这一点。然后我使用以下代码调用了该函数:

SEL selector = callbackMethod;
((void (*)(id, SEL))[callbackViewController methodForSelector:selector])(callbackViewController, selector);

我怎样才能快速完成这项工作?或者有更好的方法吗?

非常感谢您的帮助!

【问题讨论】:

  • 只是为了让您知道,您可以将 Selector 声明为字符串。示例: var mySelector : SEL = "somefunction:"
  • @tyt_g207 谢谢!这对我有用!请将此作为答案发布,以便我接受:)
  • 您的 Objective-C 代码可以简化为 ((void (*)(id, SEL))objc_msgSend)(callbackViewController, selector); 或只是 [callbackViewController performSelector:selector];
  • 继续@newacct 注释,在Swift 中可以使用下面的代码viewController.performSelector(onMainThread: selector, with: nil, waitUntilDone: false)

标签: ios swift xcode macos asynchronous


【解决方案1】:

我在以下要点中分享了我用于此场景的模式:https://gist.github.com/szehnder/84b0bd6f45a7f3f99306

基本上,我创建了一个设置 AFNetworking 客户端的单例 DataProvider.swift。然后视图控制器调用该 DataProvider 上的方法,每个方法都由我定义为名为 ServiceResponse 的类型别名的闭包终止。此闭包返回字典或错误。

它允许您非常干净地(imo)从 VC 调用异步数据操作,并非常清楚地指示您希望在该异步响应返回时执行什么。

DataProvider.swift

typealias ServiceResponse = (NSDictionary?, NSError?) -> Void

class DataProvider: NSObject {

    var client:AFHTTPRequestOperationManager?
    let LOGIN_URL = "/api/v1/login"

    class var sharedInstance:DataProvider {
        struct Singleton {
            static let instance = DataProvider()
        }
        return Singleton.instance
    }

    func setupClientWithBaseURLString(urlString:String) {
        client = AFHTTPRequestOperationManager(baseURL: NSURL.URLWithString(urlString))
        client!.operationQueue = NSOperationQueue.mainQueue()
        client!.responseSerializer = AFJSONResponseSerializer()
        client!.requestSerializer = AFJSONRequestSerializer()
    }

    func loginWithEmailPassword(email:String, password:String, onCompletion: ServiceResponse) -> Void {
        self.client!.POST(LOGIN_URL, parameters: ["email":email, "password":password] , success: {(operation:AFHTTPRequestOperation!, responseObject:AnyObject!) -> Void in

            self.setupClientWithBaseURLString("http://somebaseurl.com")

            let responseDict = responseObject as NSDictionary
                // Note: This is where you would serialize the nsdictionary in the responseObject into one of your own model classes (or core data classes)
                onCompletion(responseDict, nil)
            }, failure: {(operation: AFHTTPRequestOperation!, error:NSError!) -> Void  in
                onCompletion(nil, error)
            })
    }
}

MyViewController.swift

import UIKit

class MyViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override func viewWillAppear(animated: Bool)  {
        super.viewWillAppear(animated)
        DataProvider.sharedInstance.loginWithEmailPassword(email:"some@email.com", password:"somepassword") { (responseObject:NSDictionary?, error:NSError?) in

            if (error) {
                println("Error logging you in!")
            } else {
                println("Do something in the view controller in response to successful login!")
            }
        }
    }  
}

【讨论】:

  • 我现在已经实现了你的方法并且它有效。但是现在尝试在回调中更改 UI 时出现了一个非常奇怪的行为:例如,无法显示警报,并且当我尝试显示时控制台显示 Modifications to the layout engine must not be performed from a background thread。此外,视图本身的图形更改会延迟几秒钟,尽管与这些更改相关的代码会立即执行。我怎样才能摆脱这些问题?你有什么想法吗?
  • 我使用 dispatch_async(dispatch_get_main_queue(), { ... }) 解决了这个问题
  • 如果实际的闭包声明“ServiceResponse”的返回类型为 Void,那么说“此闭包返回字典或错误”是否准确?不是说闭包有NSDictionary类型和NSError类型两个参数吗?
  • 如果我们无论如何都要回调 javascript,为什么不也采用它的约定。 callback(err, data) 而不是 callback(data, err)
  • 将 operationQueue 属性设置为 mainQueue 队列以外的属性肯定会更好。并专门在主线程上执行完成块。解析将在 bg 队列上执行,这对于 fps 总是更好
【解决方案2】:

我想建议使用块或闭包回调而不是使用 NSThread 和选择器。

例如,在我的 API 中,我有以下方法:

斯威夫特:

您将在下面找到更新的实现。

func getUsers(completion: (result: NSArray?, error: NSError?)->())
{
    var session = NSURLSession.sharedSession()
    var task = session.dataTaskWithRequest(request){
     (data, response, error) -> Void in
       if error != nil {
         completion(nil, error)
       } else {
         var result:NSArray = data to NSArray;
         completion(result, nil)
       }
    }
    task.resume()
}

目标-C:

...
typedef void (^CBSuccessBlock)(id result);
typedef void (^CBFailureBlock)(NSError *error);
...

- (void)usersWithSucces:(CBSuccessBlock)success failure:(CBFailureBlock)failure
{
    NSURLSession *session = [NSURLSession sharedSession];
    [[session dataTaskWithURL:[NSURL URLWithString:url]
            completionHandler:^(NSData *data,
                                NSURLResponse *response,
                                NSError *error) {

                NSArray *users = //convert data to array

                if(error)
                    failure(error);
                else
                    success(users);
            }] resume];
}

然后,只需从视图控制器调用 api:

Objc:
[api usersWithSucces:^(id result)
{
   //Success callback
} failure:^(NSError *error)
{
   //Failure callback
}];

Swift:
api.getUsers({(result: AnyObject?, error: NSError?) -> Int in
    // callback here
})

更新:

同时,我看到问题和答案仍然有用且感兴趣。好吧,这是使用通用枚举作为结果对象的 swift 实现的更新版本:

//Generic enum that represents the result
enum AsyncResult<T>
{
    case Success(T)
    case Failure(NSError?)
}


class CustomUserObject
{

}

func getUsers(completion: (AsyncResult<[CustomUserObject]>)->())
{
    let request = NSURLRequest()
    let session = NSURLSession.sharedSession()
    let task = session.dataTaskWithRequest(request){
        (data, response, error) -> Void in
        if let error = error
        {
            completion(AsyncResult.Failure(error))
        } else {
            let result: [CustomUserObject] = []//deserialization json data into array of [CustomUserObject]
            completion(AsyncResult.Success(result))
        }
    }
    task.resume()
}

//Usage:

getUsers { (result) in
    switch result
    {
    case .Success(let users):
        /* work with users*/
        break
    case .Failure(let error):
        /* present an error */
        break
    }
}

【讨论】:

    【解决方案3】:

    我刚刚做了这个小例子: Swift: Async callback block pattern example

    基本上有ClassA:

    //ClassA it's the owner of the callback, he will trigger the callback when it's the time
    class ClassA {
        //The property of that will be associated to the ClassB callback
        var callbackBlock : ((error : NSError?, message : String?, adress : String? ) -> Void)?
    
        init() {
            //Do Your staff
        }
    
        //Define your function with the clousure as a parameter
        func yourFunctionWithCallback(#functionCallbackParameter : (error : NSError?,message : String?, adress : String?) -> ()) {
            //Set the calback with the calback in the function parameter
            self.callbackBlock = functionCallbackParameter
        }
    
        //Later On..
        func callbackTrigger() {
            self.callbackBlock?(error: nil,message: "Hello callback", adress: "I don't know")
    
        }
    }
    

    B类:

    //ClassB it's the callback reciver the callback
    class ClassB {
        @IBAction func testCallbackFunction(sender: UIButton) {
            let classA = ClassA()
            classA.yourFunctionWithCallback { (error, message, adress) -> () in
                //Do your stuff
            }
        }
    }
    

    ClassA: 它拥有一个属性,即回调块。 ClassB 将通过调用 yourFunctionWithCallback 函数来设置此属性。稍后ClassA就准备好了,会在callbackTrigger函数里面调用callBackBlock来触发回调。

    ClassB:会调用ClassA方法设置回调块,等待块被触发。

    【讨论】:

      【解决方案4】:

      NSThread 可以帮助你吗? :

      NSThread.detachNewThreadSelector(<#selector: Selector#>, toTarget: <#AnyObject?#>, withObject: <#AnyObject?#>) 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-08-11
        • 1970-01-01
        • 1970-01-01
        • 2020-04-30
        • 2011-08-24
        • 2017-05-15
        相关资源
        最近更新 更多