Swiftで非同期コールバックを作成するにはどうすればよいですか? iOSとOS Xの両方で実行することになっているので、アプリ用の小さなフレームワークを書いています。したがって、デバイス固有ではないメインコードをこのフレームワークに入れて、オンラインAPIへのリクエストも処理します。そして明らかに、アプリのGUIも必要なので、APIリクエストが終了するとすぐにViewControllersが反応します。 Objective-Cでは、id変数で呼び出す必要のある関数とセレクター変数の関数自体を含むビューを保存することでこれを実行しました。次に、次のコードを使用して関数を呼び出しました。
SEL selector = callbackMethod;
((void (*)(id, SEL))[callbackViewController methodForSelector:selector])(callbackViewController, selector);
どうすればこれを迅速に達成できますか?または、これを行うより良い方法はありますか?
本当にありがとうございました!
次の要点でこのシナリオに使用するパターンを共有しました: https://Gist.github.com/szehnder/84b0bd6f45a7f3f99306
基本的に、AFNetworkingクライアントをセットアップするシングルトンDataProvider.Swiftを作成します。次に、View ControllerはそのDataProviderのメソッドを呼び出します。各メソッドは、ServiceResponseと呼ばれるタイプエイリアスとして定義したクロージャーによって終了します。このクロージャは、辞書またはエラーを返します。
これにより、VCから非同期データアクションを非常にきれいに(imo)呼び出すことができ、その非同期応答が返されたときに実行する内容が非常に明確に示されます。
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!")
}
}
}
}
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];
}
次に、View Controllerから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
}
}
この小さな例を作成しました: 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")
}
}
そしてClassB:
//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:それは所有者であり、魔法使いはcallbackBlockです。 ClassBは、yourFunctionWithCallback関数を呼び出してこのプロパティを設定します。後でClassAの準備ができたら、callbackTrigger関数内でcallBackBlockを呼び出してコールバックをトリガーします。
ClassB:ClassAメソッドを呼び出してコールバックブロックを設定し、ブロックがトリガーされるまで待機します。
NSThreadはあなたを助けることができますか? :
NSThread.detachNewThreadSelector(<#selector: Selector#>, toTarget: <#AnyObject?#>, withObject: <#AnyObject?#>)