web-dev-qa-db-fra.com

Swift: rappel asynchrone

Comment puis-je faire des rappels asynchrones dans Swift? J'écris un petit Framework pour mon application car elle est supposée fonctionner sur les deux, iOS et OS X. J'ai donc mis le code principal qui n'est pas spécifique à l'appareil dans ce framework qui gère également les requêtes à mon API en ligne. Et évidemment, je veux aussi que l'interface graphique de l'application et donc mes ViewControllers réagissent dès qu'une demande d'api est terminée. Dans Objective-C, je l'ai fait en enregistrant la vue contenant la fonction qui devait être appelée dans une variable id et la fonction elle-même dans une variable de sélection. Ensuite, j'ai appelé la fonction en utilisant le code suivant:

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

Comment puis-je accomplir cela rapidement? Ou existe-t-il une meilleure façon de procéder?

J'apprécie vraiment toute ton aide!

21
stoeffn

J'ai partagé le modèle que j'utilise pour ce scénario dans le Gist suivant: https://Gist.github.com/szehnder/84b0bd6f45a7f3f99306

Fondamentalement, je crée un DataProvider.Swift singleton qui configure un client AFNetworking. Ensuite, les contrôleurs de vue appellent des méthodes sur ce DataProvider, chacune étant terminée par une fermeture que j'ai définie comme une typealias appelée ServiceResponse. Cette fermeture renvoie soit un dictionnaire soit une erreur.

Il vous permet d'appeler (imo) très proprement une action de données asynchrone à partir des VC avec une indication très claire de ce que vous voulez effectuer lorsque cette réponse asynchrone revient.

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:"[email protected]", 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!")
            }
        }
    }  
}
30
user871177

Je voudrais recommander d'utiliser un rappel de bloc ou de fermeture au lieu d'utiliser NSThread et des sélecteurs.

Par exemple, dans mon API, j'ai la méthode suivante:

Swift:

Ci-dessous vous trouverez une implémentation mise à jour.

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

Objectif-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];
}

Ensuite, il suffit d'appeler l'api depuis le contrôleur de vue:

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

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

MISE À JOUR:

En attendant, je vois que la question et les réponses sont toujours utiles et intéressées. Eh bien, voici une version mise à jour de l'implémentation Swift utilisant l'énumération générique comme objet de résultat:

//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
    }
}
24
tikhop

Je viens de faire ce petit exemple: Swift: exemple de modèle de bloc de rappel Async

Fondamentalement, il existe 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")

    }
}

Et ClasseB:

//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: c'est le propriétaire d'une propriété qui est le callbackBlock. ClassB définira cette propriété en appelant la fonction yourFunctionWithCallback. Plus tard, alors ClassA, il est prêt, déclenchera le rappel en appelant callBackBlock dans la fonction callbackTrigger.

ClassB: appellera la méthode ClassA pour définir le bloc de rappel et attendra que le bloc ait été déclenché.

4
Max_Power89

NSThread peut-il vous aider? :

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