12

これが私の問題です。アプリケーションがバックグラウンドに入ると、一定期間後に機能を実行したいと思います。これが私がすることです:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    isRunningInBackground = YES;

    taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];

    int64_t delayInSeconds = 30;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
    {
        [self doSomething];
    });
}

- (void)doSomething
{
   NSLog(@"HELLO");
}

taskIdentifier変数は、myAppDelegate.hファイルで次のように宣言されます。

UIBackgroundTaskIdentifier taskIdentifier;

すべてが想定どおりに機能します。30秒が経過した直後にコンソールがHELLOを印刷することがわかります。doSomethingしかし、30秒が経過するまでアプリがフォアグラウンドに入った場合は実行されたくありません。だからキャンセルする必要があります。これは私がそれを行う方法です:

- (void)applicationWillEnterForeground:(UIApplication *)application
{    
    isRunningInBackground = NO;
    [self stopBackgroundExecution];
}

- (void)stopBackgroundExecution
{
    [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
    taskIdentifier = UIBackgroundTaskInvalid;
}

しかし、残念ながらそれはキャンセルされませんdoSomething、それはまだ実行されます。私は何が間違っているのですか?その機能をキャンセルするにはどうすればよいですか?

4

11 に答える 11

14

なぜGCDを使用するのですか?NSTimerを使用して、アプリが前兆に戻ったときに無効にすることができます。

于 2012-09-18T11:36:50.807 に答える
12

少し異なるアプローチ でOKなので、すべての回答を収集し、考えられる解決策を考えれば、この場合(単純さを維持する)に最適な方法は、必要に応じて電話をかけ、電話performSelector:withObject:afterDelay:でキャンセルすることcancelPreviousPerformRequestsWithTarget:です。私の場合-次の遅延呼び出しをスケジュールする直前:

[NSObject cancelPreviousPerformRequestsWithTarget: self selector:@selector(myDelayedMethod) object: self];

[self performSelector:@selector(myDelayedMethod) withObject: self afterDelay: desiredDelay];
于 2015-02-04T14:17:54.680 に答える
10

dispatch_after ここでキャンセルについての質問に答えました。しかし、私が解決策を見つけるためにグーグルで検索すると、それは私をこのスレッドに戻すので...

iOS8とOSXYosemiteが導入されdispatch_block_cancel、実行を開始する前にブロックをキャンセルできるようになりました。あなたはここでその答えについての詳細を見ることができます

を使用dispatch_afterすると、その関数で作成した変数を使用することでメリットが得られ、シームレスに見えます。を使用する場合は、必要な変数をNSTimer作成しSelectorて送信するuserInfoか、その変数をグローバル変数に変換する必要があります。

于 2016-06-15T08:41:44.290 に答える
6

この回答はここに投稿する必要があります:dispatch_after()メソッドをキャンセルしますか?、しかしそれは重複として閉じられます(実際にはそうではありません)。とにかく、これはグーグルが「dispatch_aftercancel」のために返す場所なので...

この質問は非常に基本的なものであり、runloopタイマー、インスタンスに含まれるブール値、重いブロックマジックなど、さまざまなプラットフォーム固有のソリューションに頼らずに、真に汎用的なソリューションを求める人々がいると確信しています。GCDは通常のCライブラリとして使用でき、タイマーのようなものはまったくないかもしれません。

幸いなことに、任意のライフタイムスキームで任意のディスパッチブロックをキャンセルする方法があります。

  1. dispatch_afterに渡す各ブロックに動的ハンドルをアタッチする必要があります(またはdispatch_async、実際には重要ではありません)。
  2. このハンドルは、ブロックが実際に起動されるまで存在する必要があります。
  3. このハンドルのメモリ管理はそれほど明白ではありません。ブロックがハンドルを解放すると、後でダングリングポインターを逆参照する可能性がありますが、解放すると、ブロックは後でそれを実行する可能性があります。
  4. したがって、所有権をオンデマンドで渡す必要があります。
  5. 2つのブロックがあります。1つはとにかく起動する制御ブロックで、もう1つはキャンセルされる可能性のあるペイロードです。

struct async_handle {
    char didFire;       // control block did fire
    char shouldCall;    // control block should call payload
    char shouldFree;    // control block is owner of this handle
};

static struct async_handle *
dispatch_after_h(dispatch_time_t when,
                 dispatch_queue_t queue,
                 dispatch_block_t payload)
{
    struct async_handle *handle = malloc(sizeof(*handle));

    handle->didFire = 0;
    handle->shouldCall = 1; // initially, payload should be called
    handle->shouldFree = 0; // and handles belong to owner

    payload = Block_copy(payload);

    dispatch_after(when, queue, ^{
        // this is a control block

        printf("[%p] (control block) call=%d, free=%d\n",
            handle, handle->shouldCall, handle->shouldFree);

        handle->didFire = 1;
        if (handle->shouldCall) payload();
        if (handle->shouldFree) free(handle);
        Block_release(payload);
    });

    return handle; // to owner
}

void
dispatch_cancel_h(struct async_handle *handle)
{
    if (handle->didFire) {
        printf("[%p] (owner) too late, freeing myself\n", handle);
        free(handle);
    }
    else {
        printf("[%p] (owner) set call=0, free=1\n", handle);
        handle->shouldCall = 0;
        handle->shouldFree = 1; // control block is owner now
    }
}

それでおしまい。

重要な点は、「所有者」はハンドルが不要になるまでハンドルを収集する必要があるということです。dispatch_cancel_h()は、ハンドルの[潜在的に延期された]デストラクタとして機能します。

C所有者の例:

size_t n = 100;
struct after_handle *handles[n];

for (size_t i = 0; i < n; i++)
    handles[i] = dispatch_after_h(when, queue, ^{
        printf("working\n");
        sleep(1);
    });

...

// cancel blocks when lifetime is over!

for (size_t i = 0; i < n; i++) {
    dispatch_cancel_h(handles[i]);
    handles[i] = NULL; // not our responsibility now
}

Objective-C ARCの例:

- (id)init
{
    self = [super init];
    if (self) {
        queue = dispatch_queue_create("...", DISPATCH_QUEUE_SERIAL);
        handles = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)submitBlocks
{
    for (int i = 0; i < 100; i++) {
        dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (random() % 10) * NSEC_PER_SEC);

        __unsafe_unretained id this = self; // prevent retain cycles

        struct async_handle *handle = dispatch_after_h(when, queue, ^{
            printf("working (%d)\n", [this someIntValue]);
            sleep(1);
        });
        [handles addObject:[NSValue valueWithPointer:handle]];
    }
}

- (void)cancelAnyBlock
{
    NSUInteger i = random() % [handles count];
    dispatch_cancel_h([handles[i] pointerValue]);
    [handles removeObjectAtIndex:i];
}

- (void)dealloc
{
    for (NSValue *value in handles) {
        struct async_handle *handle = [value pointerValue];
        dispatch_cancel_h(handle);
    }
    // now control blocks will never call payload that
    // dereferences now-dangling self/this.
}

ノート:

  • dispatch_after()は元々キューを保持しているため、すべての制御ブロックが実行されるまでキューは存在します。
  • ペイロードがキャンセルされた場合(または所有者の存続期間が終了した場合)、および制御ブロックが実行された場合、async_handlesは解放されます。
  • async_handleの動的メモリオーバーヘッドは、dispatch_after()およびdispatch_queue_tの内部構造と比較して、絶対にわずかです。これらの内部構造は、送信されるブロックの実際の配列を保持し、必要に応じてそれらをデキューします。
  • shouldCallとshouldFreeが実際には同じ反転フラグであることに気付くかもしれません。ただし、所有者インスタンスは、「自己」または他の所有者関連データに依存しない場合、ペイロードブロックを実際にキャンセルせずに、所有権を渡し、さらには-[dealloc]自体を渡すことができます。これは、dispatch_cancel_h()への追加のshouldCallAnyway引数を使用して実装できます。
  • 警告注:このソリューションにはdidXYZフラグの同期も欠けており、制御ブロックとキャンセルルーチンの間で競合が発生する可能性があります。OSAtomicOr32Barrier()&coを使用して同期します。
于 2015-05-14T23:27:25.853 に答える
5

iOS10とSwift3GCDはキャンセル可能です。DispatchWorkItem作業項目のインスタンスを保持し、キャンセルされていないかどうかを確認してから、次の手順を実行します。

// Create a work item
let work = DispatchWorkItem {
    print("Work to be done or cancelled")
}

// Dispatch the work item for executing after 2 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: work)

// Later cancel the work item
if !work.isCancelled {
    work.cancel()
}
于 2016-10-23T18:35:14.493 に答える
3

endBackgroundTaskバックグラウンドタスクをキャンセルしません。バックグラウンドタスクが終了したことをシステムに通知します。したがって、「何かをした」後にこれを呼び出す必要があります。doSomethingアプリが再びフォアグラウンドにある場合に実行されないようにするには、フラグを使用できますisRunningInBackground

dispatch_after(popTime, dispatch_get_global_queue(...), ^(void) {
    if (isRunningInBackground) {
        [self doSomething];
    }
    [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
});
于 2012-09-18T11:25:24.547 に答える
2

キャンセルすることはできないと思いますが、doSomethingを実行する前にタスクの状態を確認することはできます

dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
  {

    if(taskIdentifier != UIBackgroundTaskInvalid) {
        [self doSomething];
    }

  });
于 2012-09-18T11:27:27.140 に答える
2

https://developer.apple.com/documentation/dispatch/1431058-dispatch_block_cancel

dispatch_block_cancel 指定されたディスパッチブロックを非同期的にキャンセルします。

void dispatch_block_cancel(dispatch_block_t block);

(iOS 8.0以降、macOS 10.10以降、tvOS 9.0以降、watchOS 2.0以降)

于 2019-05-28T13:13:18.473 に答える
1

フラグで絶対キャンセルできます。私はそれを行うための小さな関数を書きました。基本的には、ブロックがキャンセルされるかどうかを制御するためにBOOLポインターを渡します。

void dispatch_with_cancellation(void (^block)(), BOOL* cancellation) {
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{
        if (!*cancellation) {
            block();
        }
    });
}

int main(int argc, char *argv[]) {
    @autoreleasepool {
        void (^block)() = ^{
            NSLog(@"%@", @"inside block");
        };
        BOOL cancellation;
        dispatch_with_cancellation(block, &cancellation);
        // cancel the block by setting the BOOL to YES.
        *&cancellation = YES;
        [[NSRunLoop currentRunLoop] run];
    }
}
于 2016-05-06T23:49:53.463 に答える
1

これはやや一般的な応答ですが、それでもあなたの質問にかなりよく答えると思います。「isRunningInBackground」の代わりに、最後にバックグラウンド/フォアグラウンドした時間を保持します。バックグラウンドしていた時間をdispatch_afterのローカル変数として使用します。doSomethingを呼び出す前に、dispatch_afterの内部を確認してください。以下の私のより具体的な問題...

さまざまなタイミングで起動する必要があり、モデルレイヤーが適切なタイミングでプレゼンテーションレイヤーに更新されていることを確認しながらsetBeginTimeを使用すると、お互いに踏みにじるアニメーションの山をたくさんやっています...それで始めましたそれらを「キャンセル」できなかったことを除いて、dispatch_afterを使用します(これは、一連のアニメーションを再開したいときに特に重要でした)。

CFTimeInterval startCalled;UIViewインスタンスを保持しているので、内部には次の-(void) startものがあります。

startCalled = CACurrentMediaTime();
CFTimeInterval thisStartCalled = startCalled;

dispatch_afterブロックの開始時に、次のようになります。

if (thisStartCalled != startCalled) return;

これにより、すべてを一度にセットアップできますが、モデルレイヤーは、開始する予定のときにCATransactionブロック内でのみ更新れます。

于 2016-07-19T23:44:16.550 に答える
0

メインスレッドをブロックせずに検索をデバウンスするために、以下のアプローチを使用しました。

ブロックを格納するグローバル変数を作成します

dispatch_block_t searchBlock;

存在するかどうかを確認してキャンセルします(まだディスパッチされていない場合のみ)

   if (searchBlock) {
        dispatch_block_cancel(searchBlock);
    }

ブロックを定義する

    searchBlock = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{
        //Do Something
    });

実行する

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), serialQueue, searchBlock);
于 2021-04-06T11:12:22.457 に答える