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!
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!")
}
}
}
}
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
}
}
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é.
NSThread peut-il vous aider? :
NSThread.detachNewThreadSelector(<#selector: Selector#>, toTarget: <#AnyObject?#>, withObject: <#AnyObject?#>)