5

久しぶりの潜伏者、初めてのポスター。私は客観的 C に比較的慣れていないので、かなり単純なことを尋ねている場合は申し訳ありません。私のグーグルとスタックオーバーフローはここで私を失望させたので、誰かが助けてくれるかもしれないと思った.

たとえば、3 つの関数を連続して実行する同期プロセスがあります。それを A -> B-> C と呼びます。ここで、タスク A が実行され、その後に B が実行され、その後に C が実行されます。

ここで、B には、完了のためのデリゲート コールバックを伴う非同期プロセスが含まれます。しかし、B は C が実行される前に完了する必要があるため、B が完了する前に C がトリガーされないようにするメカニズムが必要です。この問題には共通の設計パターンが必要だと思いますか?

最初は素朴な解決策は-

execute A
execute B
while (!B finished) {}
execute C

...しかし、これは本当に不自由なようです。

ある種のブロックでこれを行うことができると思いますが、私の人生では、それを理解することはできません. 誰でも助けてもらえますか?

任意の支援に感謝!

ギヨーム

4

6 に答える 6

5

フィードバックをお寄せいただきありがとうございます。すぐに返信できなかったことをお詫び申し上げます。私は今、提案とは少し異なる方法でこれを解決しました:

まず、次のメソッドを持つように NSObject を拡張しました -

#import "NSObject+LTExtensions.h"

@implementation NSObject (Testing)

- (void) performSelectorWithBlock: (SEL) selector withSemaphore:(dispatch_semaphore_t)semaphore
{
  [self performSelector:selector]; // This selector should complete the semaphore
  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  dispatch_release(semaphore);
}

@end

これにより、セレクターを介してブロックを実行できます。ブロックが実行されると、ブロックが実行されるスレッドは、特定のディスパッチ セマフォによって続行するように通知されるまで待機します。

次にできることは次のとおりです。

  • コールA
  • ディスパッチ セマフォを作成し、B を実行するセレクタを定義します。
  • 上で定義したメソッドを呼び出して B を実行し、セレクターが完了するのを待ちます
  • B が (デリゲート コールバックを介して) 完了すると、ディスパッチ セマフォに信号を送って待機を一時停止します。
  • 次に、Cを実行します

だから私たちは持っています

A
B -> Asynchronous with delegate callback
C 

上記の実装方法の簡単な例を次に示します

-(void) methodA {

  // ... do something

  // Assign your semaphore (this is a dispatch_semaphore_t)
  self.semaphore = dispatch_semaphore_create(0);
  [self performSelectorWithBlock:@selector(methodB) withSemaphore:semaphore];
  [self methodC];
}

-(void) methodB {
  // ... do whatever needs to be done asynchronously
  CFRunLoopRun();
}

-(void) methodBDelegateCallBack {
  // This is called when B completes

  // Signal completion
  dispatch_semaphore_signal(self.semaphore);
  CFRunLoopStop(CFRunLoopGetCurrent());
}

-(void) methodC {
 ...
}

問題なく非常にうまく機能します (ただし、私は Obj C を初めて使用するため、私のアプローチには明らかな問題がある可能性があります)。

于 2012-09-07T12:58:07.480 に答える
2

この問題に対する別のアプローチとして、非同期タスクのヘルパー オブジェクトを作成し、タスクが呼び出されたときに完了ブロックをコピーするという方法があります。非同期タスクが終了したら、デリゲート メソッドを使用して完了ブロックを呼び出します。その結果、次のような順序でタスクを実行できます。

FSTask      *taskA = [FSTask taskWithName:@"Task A"];
FSAsyncTask *taskB = [FSAsyncTask asyncTaskWithName:@"Task B"];
FSTask      *taskC = [FSTask taskWithName:@"Task C"];


[taskA performTaskWithCompletionBlock:^ (NSString *result) {
    NSLog(@"%@", result);

    [taskB performTaskWithCompletionBlock:^ (NSString *result) {
        NSLog(@"%@", result);

        [taskC performTaskWithCompletionBlock:^ (NSString *result) {
            NSLog(@"%@", result);

        }];
    }];
}];

では、これはどのように達成されるのでしょうか。さて、下のタスクオブジェクトを見てください...


FSTask.m -メイン スレッドでの同期作業 ...

@interface FSTask ()

@property (nonatomic, copy) NSString *name;

@end


@implementation FSTask

@synthesize name = _name;

+ (FSTask *)taskWithName:(NSString *)name
{
    FSTask *task = [[FSTask alloc] init];
    if (task)
    {
        task.name = name;
    }
    return task;
}

- (void)performTaskWithCompletionBlock:(void (^)(NSString *taskResult))block
{
    NSString *message = [NSString stringWithFormat:@"%@: doing work on main thread ...", _name];

    NSLog(@"%@", message);

    if (block)
    {
        NSString *result = [NSString stringWithFormat:@"%@: result", _name];
        block(result);
    }
}

@end

FSAsyncTask.m -バックグラウンド スレッドでの非同期作業 ...

@interface FSAsyncTask ()

@property (nonatomic, copy) void (^block)(NSString *taskResult);
@property (nonatomic, copy) NSString *name;

- (void)performAsyncTask;

@end



@implementation FSAsyncTask

@synthesize block = _block;
@synthesize name  = _name;

+ (FSAsyncTask *)asyncTaskWithName:(NSString *)name
{
    FSAsyncTask *task = [[FSAsyncTask alloc] init];
    if (task)
    {
        task.name = name;
    }
    return task;
}

- (void)performTaskWithCompletionBlock:(void (^)(NSString *taskResult))block
{
    self.block = block;


    // the call below could be e.g. a NSURLConnection that's being opened,
    //  in this case a NSURLConnectionDelegate method will return the result
    //  in this delegate method the completion block could be called ...

    dispatch_queue_t queue = dispatch_queue_create("com.example.asynctask", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^ {

        [self performAsyncTask];

    });
}

#pragma mark - Private

- (void)performAsyncTask
{
    for (int i = 0; i < 5; i++)
    {
        NSString *message = [NSString stringWithFormat:@"%d - %@: doing work on background thread ...", i, _name];
        NSLog(@"%@", message);

        [NSThread sleepForTimeInterval:1];
    }


    // this completion block might be called from your delegate methods ...

    if (_block)
    {
        dispatch_async(dispatch_get_main_queue(), ^ {

            NSString *result = [NSString stringWithFormat:@"%@: result", _name];
            _block(result);

        });
    }
}

@end
于 2012-09-07T13:39:41.600 に答える
1

ブロックプロパティをBに割り当てることができます。このプロパティは、デリゲートメソッドを呼び出す前にコードのブロックを実行するために使用されます。何かのようなもの:

@property(nonatomic、copy)void(^ yourBlock)(id blockParameter);

したがって、Bのデリゲートを呼び出した後、このブロックを呼び出して実行できます。このブロック内で、Cのメソッドを呼び出すことができます。

于 2012-08-29T18:21:23.117 に答える
1

私がこれを処理した方法は次のとおりです。

非同期呼び出しの前に NSMutableDictionary を作成しました。

次に、非同期呼び出しを行います。待っている値をチェックします

NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[AsyncCallClass asyncCall:^{
    @synchronized(dictionary) {
        [dictionary setValue:myValue forKey:@"result"];
    }
}];

while (true){
    @synchronized(dictionary){
        if ([dictionary valueForKey:@"resultValue"] != nil){
            break;
        }
    }
    [NSThread sleepForTimeInterval:.25];
}
MyResultClass *result = [dictionary valueForKey:@"resultValue"];

これにもタイムアウトを追加して、無限ループにならないようにすることができます。しかし、これが私の解決策です。そしてそれはかなりうまくいくようです。

于 2012-08-29T18:22:46.930 に答える
1

これは、私がそのようなことを行うために使用する典型的なコードです(もちろん、completionBlock シグネチャとメソッド名をニーズに合わせて調整してください)。

typedef void (^BCompletionBlock)(void);

@interface B : NSObject <BDelegate>
@property(nonatomic, copy) BCompletionBlock completionBlock;
-(void)doAsynchronousActionWithCompletion:(BCompletionBlock)aCompletionBlock;
@end

@implementation B
-(void)doAsynchronousActionWithCompletion:(BCompletionBlock)aCompletionBlock
{
    // Store the completion block for later use
    self.completionBlock = aCompletionBlock;
    // Then execute your asynchronous action, that will call some delegate method when done
    [self doYourAsynchronousActionWithDelegate:self];
}

-(void)yourBDelegateMethodCalledWhenDone
{
    // Upon your async task completion, call your completion block then
    if (self.completionBlock) self.completionBlock();
}
@end

次に、使用例を次に示します。

-(void)doActions
{
    [a doSynchronousAction];
    [b doAsynchronousActionWithCompletion:^{
       [c doSynchronousAction];
       // A,B,C are now done
    }];
}

デリゲート メソッドを使用するアクションを (いつ完了したかを通知するため)、completeBlocks を使用するアクション (たとえば、UIAlertViews、UIActionsSheets、およびその他の多くのケースでこれを行うクラスがいくつかあります) に「変換」するために、私はこれをかなり頻繁に行っています。それは魅力のように機能します。

このような場合、デリゲート メカニズムよりも completionBlocks を使用する方がはるかに簡単です。

于 2012-09-07T13:14:01.453 に答える
0

次のように C をブロックで渡すこともできます...

カスタム ブロックを定義する

typedef void(^myCompletion)(BOOL complete);

B メソッドを作成する

-(void)performBWithCompletionBlock:(myCompletion)complete;
{
  // do your things

  [self.delegate delegateCallback];
  complete(YES);

}

次に、BG / async ABC を作成します

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // now we're on a BG queue to perform our async tasks

             [self performA];
             [self performBWithCompletionBlock:^(BOOL complete) {

                if (complete == YES)
                    [self performC];
             }];
        });

Cをメインスレッドにしたい場合

dispatch_async(dispatch_get_main_queue(), ^{
    [self performC];
});
于 2014-11-10T18:35:09.573 に答える