別のブロックを開始する前に、2つの非同期ブロックが実行されるまで待機する


192

GCDを使用する場合、2つの非同期ブロックが実行されて完了するまで待ってから、次の実行ステップに進みます。それを行う最良の方法は何ですか?

以下を試しましたが、うまくいかないようです:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block1
});


dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block2
});

// wait until both the block1 and block2 are done before start block3
// how to do that?

dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block3
});

問題を解決するための最大6つの異なる方法を提供するSwift 5 に対する私の回答を参照しください。
Imanou Petit

回答:


301

ディスパッチグループを使用する:AppleのiOS開発者ライブラリの同時実行プログラミングガイドの「ディスパッチキュー」の章にある「キューに入れられたタスクのグループの待機」の例については、こちらを参照してください。

あなたの例は次のようになります:

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block1
    NSLog(@"Block1");
    [NSThread sleepForTimeInterval:5.0];
    NSLog(@"Block1 End");
});


dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block2
    NSLog(@"Block2");
    [NSThread sleepForTimeInterval:8.0];
    NSLog(@"Block2 End");
});

dispatch_group_notify(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block3
    NSLog(@"Block3");
});

// only for non-ARC projects, handled automatically in ARC-enabled projects.
dispatch_release(group);

次のような出力を生成できます:

2012-08-11 16:10:18.049 Dispatch[11858:1e03] Block1
2012-08-11 16:10:18.052 Dispatch[11858:1d03] Block2
2012-08-11 16:10:23.051 Dispatch[11858:1e03] Block1 End
2012-08-11 16:10:26.053 Dispatch[11858:1d03] Block2 End
2012-08-11 16:10:26.054 Dispatch[11858:1d03] Block3

3
涼しい。グループに関連付けられた非同期タスク/ブロックは、順次または同時に実行されますか?つまり、block1とblock2がグループに関連付けられているとします。block2は、block1が完了するまで待機してから実行を開始できますか?
tom

9
それはあなた次第です。グループパラメータを追加したdispatch_group_async場合dispatch_asyncと同じです。したがって、block1とblock2に異なるキューを使用するか、同じ並行キューでそれらをスケジュールすると、それらは同時に実行できます。それらを同じシリアルキューでスケジュールすると、それらはシリアルに実行されます。グループなしでブロックをスケジュールするのと同じです。
ジョーンEyrich

1
これはWebサービスの投稿の実行にも適用されますか?
SleepNot 14

時間はブロックに設定されたスリープ時間と等しくないことに気づきましたか?なぜそれはこのようになるのでしょうか?
デイモンユアン

2
ARCでは、dispatch_release(group)を削除するだけです。
loretoparisi 2015

272

dispatch_async非同期完了ブロックの場合のようにブロックの呼び出しを制御できない場合は、JörnEyrichの回答(これを支持する場合は彼の回答を支持)を拡張してdispatch_group_enter、およびをdispatch_group_leave直接使用してGCDグループを使用できます。

この例では、 computeInBackground変更できないもの(デリゲートコールバック、NSURLConnection completionHandlerなど)をているため、ディスパッチコールにアクセスできません。

// create a group
dispatch_group_t group = dispatch_group_create();

// pair a dispatch_group_enter for each dispatch_group_leave
dispatch_group_enter(group);     // pair 1 enter
[self computeInBackground:1 completion:^{
    NSLog(@"1 done");
    dispatch_group_leave(group); // pair 1 leave
}];

// again... (and again...)
dispatch_group_enter(group);     // pair 2 enter
[self computeInBackground:2 completion:^{
    NSLog(@"2 done");
    dispatch_group_leave(group); // pair 2 leave
}];

// Next, setup the code to execute after all the paired enter/leave calls.
//
// Option 1: Get a notification on a block that will be scheduled on the specified queue:
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    NSLog(@"finally!");
});

// Option 2: Block an wait for the calls to complete in code already running
// (as cbartel points out, be careful with running this on the main/UI queue!):
//
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // blocks current thread
// NSLog(@"finally!");

この例では、computeInBackground:completion:は次のように実装されています。

- (void)computeInBackground:(int)no completion:(void (^)(void))block {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"%d starting", no);
        sleep(no*2);
        block();
    });
}

出力(実行のタイムスタンプ付き):

12:57:02.574  2 starting
12:57:02.574  1 starting
12:57:04.590  1 done
12:57:06.590  2 done
12:57:06.591  finally!

1
@ɲeuroburɳ上記のコードはメインスレッドで待機します。これによりメインスレッドがブロックされ、グループ全体が完了するまでUIが応答しなくなると思います。待機をバックグラウンドスレッドに移動することをお勧めします。たとえば、dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH、0)
cbartel

2
@cbartel、グッドキャッチ!コメントを反映するようにサンプルコードを更新しました。多くの場合、コールバックをメインキューに配置する必要があります。その場合dispatch_queue_notifyは、ブロッキング時間が短いことが保証されている場合を除いて、より良い方法です。
ɲeuroburɳ

どこでグループを解放できますか(つまり、dispatch_release(group))?dispatch_group_notifyで解放しても安全かどうかはわかりません。しかし、それはグループが完了した後に実行されるコードであるため、どこにリリースするかわかりません。
GingerBreadMane 2015

あなたはARCを使用している場合は、コールdispatch_releaseする必要はありません:stackoverflow.com/questions/8618632/...を
ɲeuroburɳ

3
:さらにと説明しているニースのポストcommandshift.co.uk/blog/2014/03/19/...
Rizon

96

Swift 5.1では、Grand Central Dispatchが問題を解決する多くの方法を提供します。必要に応じて、次のPlaygroundスニペットに示す7つのパターンのいずれかを選択できます。


#1。使用してDispatchGroupDispatchGroupさんnotify(qos:flags:queue:execute:)DispatchQueueさんasync(group:qos:flags:execute:)

Apple Developer Concurrency Programming Guide は次のように述べていDispatchGroupます:

ディスパッチグループは、1つ以上のタスクの実行が完了するまでスレッドをブロックする方法です。この動作は、指定されたすべてのタスクが完了するまで進行できない場所で使用できます。たとえば、いくつかのタスクをディスパッチしてデータを計算した後、グループを使用してそれらのタスクを待機し、完了したら結果を処理します。

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let group = DispatchGroup()

queue.async(group: group) {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

queue.async(group: group) {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

group.notify(queue: queue) {
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

#2。使用してDispatchGroupDispatchGroupさんwait()DispatchGroupさんenter()DispatchGroupさんleave()

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let group = DispatchGroup()

group.enter()
queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
    group.leave()
}

group.enter()
queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
    group.leave()
}

queue.async {
    group.wait()
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

あなたも混在させることができることに注意DispatchGroup wait()してDispatchQueue async(group:qos:flags:execute:)か、ミックスDispatchGroup enter()し、DispatchGroup leave()DispatchGroup notify(qos:flags:queue:execute:)


#3。使用するDispatch​Work​Item​Flags barrierDispatchQueueasync(group:qos:flags:execute:)

Swift 4のGrand Central Dispatchチュートリアル:パート1/2 Raywenderlich.comの記事は、バリアの定義を示しています。

ディスパッチバリアは、同時実行キューを操作するときにシリアルスタイルのボトルネックとして機能する関数のグループです。をDispatchWorkItemディスパッチキューに送信するときに、フラグを設定して、その特定の時間に指定されたキューで実行される唯一のアイテムであることを示すことができます。つまり、ディスパッチバリアの前にキューに送信されたすべてのアイテムは、DispatchWorkItemが実行される前に完了する必要があります。

使用法:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)

queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

queue.async(flags: .barrier) {
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

#4。使用してDispatchWorkItemDispatch​Work​Item​FlagsさんbarrierDispatchQueueさんasync(execute:)

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)

queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

let dispatchWorkItem = DispatchWorkItem(qos: .default, flags: .barrier) {
    print("#3 finished")
}

queue.async(execute: dispatchWorkItem)

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

#5。使用してDispatchSemaphoreDispatchSemaphoreさんwait()DispatchSemaphoreさんsignal()

Soroush Khanlouは次の行を書きました GCDハンドブックのブログ投稿。

セマフォを使用すると、別のスレッドからのシグナルが送信されるまで、スレッドを任意の時間ブロックできます。GCDの他の部分と同様に、セマフォはスレッドセーフであり、どこからでもトリガーできます。セマフォは、同期化する必要がある非同期APIがある場合に使用できますが、変更することはできません。

Apple Developer APIリファレンスでは、DispatchSemaphore init(value:​)イニシャライザに関する次の説明も提供しています。

値にゼロを渡すと、2つのスレッドが特定のイベントの完了を調整する必要がある場合に役立ちます。ゼロより大きい値を渡すと、リソースの有限のプールを管理するのに役立ちます。この場合、プールサイズは値と同じです。

使用法:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 0)

queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
    semaphore.signal()
}

queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
    semaphore.signal()
}

queue.async {
    semaphore.wait()
    semaphore.wait()    
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

#6。使用するOperationQueueOperationaddDependency(_:)

Apple Developer APIリファレンスには、Operation​Queue次のように記載されています。

操作キューは、 libdispatchライブラリ(Grand Central Dispatchとも呼ばれ、操作の実行を開始します。

使用法:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let operationQueue = OperationQueue()

let blockOne = BlockOperation {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

let blockTwo = BlockOperation {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

let blockThree = BlockOperation {
    print("#3 finished")
}

blockThree.addDependency(blockOne)
blockThree.addDependency(blockTwo)

operationQueue.addOperations([blockThree, blockTwo, blockOne], waitUntilFinished: false)

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 or
 #2 started
 #1 started
 #2 finished
 #1 finished
 #3 finished
 */

#7。OperationQueueおよびOperationQueueの使用addBarrierBlock(_:)(iOS 13が必要)

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let operationQueue = OperationQueue()

let blockOne = BlockOperation {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

let blockTwo = BlockOperation {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

operationQueue.addOperations([blockTwo, blockOne], waitUntilFinished: false)
operationQueue.addBarrierBlock {
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 or
 #2 started
 #1 started
 #2 finished
 #1 finished
 #3 finished
 */

それぞれにgroup.enter()とgroup.leave()を使用せずに(セマフォなしに)非同期呼び出しの解決策はありますか?サーバーへの非同期リクエストを待機する必要がある場合、その後、2番目の非同期リクエストを待機します。私はこの記事を読んだavanderlee.com/swift/asynchronous-operationsを私ドンさんはBlockOperationと比較しての簡単な使用方法を参照
ウーフ

58

別のGCDの代替案は障壁です。

dispatch_queue_t queue = dispatch_queue_create("com.company.app.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{ 
    NSLog(@"start one!\n");  
    sleep(4);  
    NSLog(@"end one!\n");
});

dispatch_async(queue, ^{  
    NSLog(@"start two!\n");  
    sleep(2);  
    NSLog(@"end two!\n"); 
});

dispatch_barrier_async(queue, ^{  
    NSLog(@"Hi, I'm the final block!\n");  
});

並行キューを作成し、2つのブロックをディスパッチしてから、バリア付きの最後のブロックをディスパッチして、他の2つのブロックが完了するのを待ちます。


sleep(4)を使用しなかった場合、問題はありませんか?
Himanth 2017年

いいえ、もちろん問題ありません。実際には、あなたはほとんどしたくありませんsleep()!これらのsleep()呼び出しは、ブロックが同時に実行されることがわかるように十分長く実行できるように、教育上の理由でのみ追加しました。この簡単な例では、がない場合sleep()、これらの2つのブロックは非常に高速に実行される可能性があるため、同時実行を経験的に観察する前に、ディスパッチされたブロックが開始および終了する可能性があります。ただしsleep()、独自のコードでは使用しないでください。
Rob

39

私はあなたがGCDについて尋ねたのを知っています、しかしあなたが望むなら、NSOperationQueueこの種のものも本当に優雅に扱います、例えば:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Starting 3");
}];

NSOperation *operation;

operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Starting 1");
    sleep(7);
    NSLog(@"Finishing 1");
}];

[completionOperation addDependency:operation];
[queue addOperation:operation];

operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Starting 2");
    sleep(5);
    NSLog(@"Finishing 2");
}];

[completionOperation addDependency:operation];
[queue addOperation:operation];

[queue addOperation:completionOperation];

3
NSBlockOperation内のコードが同期の場合、これは問題ありません。しかし、そうではなく、非同期操作が完了したときに完了をトリガーしたい場合はどうでしょうか?
Greg Maletic 2013年

3
@GregMaleticその場合、NSOperation並行isFinished処理され、非同期プロセスが完了すると設定されるサブクラスを作成します。その後、依存関係は正常に機能します。
Rob

@GregMaletic 例については、stackoverflow.com / questions / 18429011 /… およびstackoverflow.com/questions/17426855/…を参照してください。
Rob

1
@GregMaleticええ、それも使用できます(dispatch_semaphore_waitメインキューで発生していない限り、シグナルと待機のバランスが取れている限り)。メインキューをブロックしない限り、操作の柔軟性が必要ない場合は、セマフォのアプローチで問題ありません(操作をキャンセルする機能、同時実行の度合いを制御する機能など)。
Rob、

1
@ Reza.Ab-タスク2が開始する前にタスク1を完了する必要がある場合は、それらのタスク間に依存関係を追加します。または、キューが常に一度に1つのタスクのみを実行している場合は、に設定maxConcurrentOperationCountして、それをシリアルキューにします1。あなたも、業務の優先順位を設定することができ、両方qualityOfServicequeuePriority、これらは依存性および/またはキューの同時実行の程度よりもタスクの優先度にはるかに微妙な影響を与えます。
ロブ

4

上記の回答はすべてすばらしいですが、すべて1つ欠けていました。groupは、dispatch_group_enter/ を使用したときに入ったスレッドでタスク(ブロック)を実行しますdispatch_group_leave

- (IBAction)buttonAction:(id)sender {
      dispatch_queue_t demoQueue = dispatch_queue_create("com.demo.group", DISPATCH_QUEUE_CONCURRENT);
      dispatch_async(demoQueue, ^{
        dispatch_group_t demoGroup = dispatch_group_create();
        for(int i = 0; i < 10; i++) {
          dispatch_group_enter(demoGroup);
          [self testMethod:i
                     block:^{
                       dispatch_group_leave(demoGroup);
                     }];
        }

        dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
          NSLog(@"All group tasks are done!");
        });
      });
    }

    - (void)testMethod:(NSInteger)index block:(void(^)(void))completeBlock {
      NSLog(@"Group task started...%ld", index);
      NSLog(@"Current thread is %@ thread", [NSThread isMainThread] ? @"main" : @"not main");
      [NSThread sleepForTimeInterval:1.f];

      if(completeBlock) {
        completeBlock();
      }
    }

これは、作成された並行キューで実行されますdemoQueue。キューを作成しない場合は、メインスレッドで実行されます

- (IBAction)buttonAction:(id)sender {
    dispatch_group_t demoGroup = dispatch_group_create();
    for(int i = 0; i < 10; i++) {
      dispatch_group_enter(demoGroup);
      [self testMethod:i
                 block:^{
                   dispatch_group_leave(demoGroup);
                 }];
    }

    dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
      NSLog(@"All group tasks are done!");
    });
    }

    - (void)testMethod:(NSInteger)index block:(void(^)(void))completeBlock {
      NSLog(@"Group task started...%ld", index);
      NSLog(@"Current thread is %@ thread", [NSThread isMainThread] ? @"main" : @"not main");
      [NSThread sleepForTimeInterval:1.f];

      if(completeBlock) {
        completeBlock();
      }
    }

別のスレッドでタスクを実行する3つ目の方法があります。

- (IBAction)buttonAction:(id)sender {
      dispatch_queue_t demoQueue = dispatch_queue_create("com.demo.group", DISPATCH_QUEUE_CONCURRENT);
      //  dispatch_async(demoQueue, ^{
      __weak ViewController* weakSelf = self;
      dispatch_group_t demoGroup = dispatch_group_create();
      for(int i = 0; i < 10; i++) {
        dispatch_group_enter(demoGroup);
        dispatch_async(demoQueue, ^{
          [weakSelf testMethod:i
                         block:^{
                           dispatch_group_leave(demoGroup);
                         }];
        });
      }

      dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
        NSLog(@"All group tasks are done!");
      });
      //  });
    }

もちろん、前述のように使用できます dispatch_group_async目的のものを取得。


3

最初の答えは本質的に正しいですが、目的の結果を達成するための非常に単純な方法が必要な場合は、セマフォを使用してそれを行う方法を示すスタンドアロンコードの例を次に示します(これは、ディスパッチグループがバックグラウンドで動作する方法JFYIでもあります)。 :

#include <dispatch/dispatch.h>
#include <stdio.h>

main()
{
        dispatch_queue_t myQ = dispatch_queue_create("my.conQ", DISPATCH_QUEUE_CONCURRENT);
        dispatch_semaphore_t mySem = dispatch_semaphore_create(0);

        dispatch_async(myQ, ^{ printf("Hi I'm block one!\n"); sleep(2); dispatch_semaphore_signal(mySem);});
        dispatch_async(myQ, ^{ printf("Hi I'm block two!\n"); sleep(4); dispatch_semaphore_signal(mySem);});
        dispatch_async(myQ, ^{ dispatch_semaphore_wait(mySem, DISPATCH_TIME_FOREVER); printf("Hi, I'm the final block!\n"); });
        dispatch_main();
}

7
2つの観察:1.が欠落していdispatch_semaphore_waitます。2つのシグナルがあるため、2つの待機が必要です。現状のままで、「完了」ブロックは、最初のブロックがセマフォに信号を送るとすぐに、他のブロックが完了する前に開始します。2.これはiOSの質問だったので、の使用はお勧めしませんdispatch_main
Rob、

1
私はロブに同意します。これは有効なソリューションではありません。dispatch_semaphore_wait次第のいずれかとしてブロックを解除しますdispatch_semaphore_signalメソッドが呼ばれます。これが機能するように見える理由は、printfforブロックの「1」と「2」がすぐに発生printfし、「finally」のforが待機後に発生するためです。つまり、ブロック1が2秒間スリープした後です。sleep呼び出しの後にprintfを配置すると、「one」の出力が表示され、2秒後に「finally」、2秒後に「two」の出力が得られます。
ɲeuroburɳ

1

迅速に受け入れられた答え:

let group = DispatchGroup()

group.async(group: DispatchQueue.global(qos: .default), execute: {
    // block1
    print("Block1")
    Thread.sleep(forTimeInterval: 5.0)
    print("Block1 End")
})


group.async(group: DispatchQueue.global(qos: .default), execute: {
    // block2
    print("Block2")
    Thread.sleep(forTimeInterval: 8.0)
    print("Block2 End")
})

dispatch_group_notify(group, DispatchQueue.global(qos: .default), {
    // block3
    print("Block3")
})

// only for non-ARC projects, handled automatically in ARC-enabled projects.
dispatch_release(group)

0

Swift 4.2の例:

let group = DispatchGroup.group(count: 2)
group.notify(queue: DispatchQueue.main) {
     self.renderingLine = false
     // all groups are done
}
DispatchQueue.main.async {
    self.renderTargetNode(floorPosition: targetPosition, animated: closedContour) {
        group.leave()
        // first done
    }
    self.renderCenterLine(position: targetPosition, animated: closedContour) {
        group.leave()
        // second done
    }
 }

group.leave()クラッシュを引き起こした
ベン

-3

他の回答が特定の状況に適していないことは言うまでもありませんが、これは私が常にGoogleから使用しているスニペットの1つです。

- (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {


    if (signInDoneSel) {
        [self performSelector:signInDoneSel];
    }

}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.