2012-09-18 53 views
10

これは私の問題です。私のアプリケーションがバックグラウンドに入ると、一定の時間が経過してから機能を実行したいと思っています。これは私が何をすべきかです:それは、私はそのコンソールプリントハローちょうど30秒後を見ることになってdispatch_after()バックグラウンドタスクが実行されないようにします。

UIBackgroundTaskIdentifier taskIdentifier; 

すべての作品:

- (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ファイルで宣言されています去ってしまった。しかし、30秒が経過するまでアプリがフォアグラウンドに入ると、doSomethingは実行されたくありません。だから私はそれをキャンセルする必要があります。

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

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

しかし残念ながら、それはそれはまだ行われ、doSomethingをキャンセルしていない:これは、私はそれを行う方法です。私は間違って何をしていますか?どうすればその機能をキャンセルできますか?

答えて

13

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

+0

あなたが正しいです! –

+0

ありがとう!とても簡単!私は自分自身について考えていたはずです –

3

endBackgroundTaskバックグラウンドタスクをキャンセルしません。あなたのバックグラウンドタスクが完了したことをシステムに伝えます。だから、「何かをやった後」と呼ぶべきです。私はあなたがそれを取り消すことはできないと思います

dispatch_after(popTime, dispatch_get_global_queue(...), ^(void) { 
    if (isRunningInBackground) { 
     [self doSomething]; 
    } 
    [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier]; 
}); 
+0

これは、それがよりよい解決策正解 – juancazalla

2

ていますが、doSomethingの

を実行する前に、タスクの状態を確認することができます:あなたのアプリが再び前面にある場合、あなたはあなたの isRunningInBackgroundフラグを使用することができ、実行されてから doSomethingを防ぐために、
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) 
    { 

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

    }); 
11

少し異なったアプローチ OK、そう、すべての答えを収集し、可能な解決策では、このような場合のために最良のもののように思えるたいとき(シンプルさを維持)performSelector:withObject:afterDelay:を呼び出し、cancelPreviousPerformRequestsWithTarget:コールでそれを打ち消しています。この答えはここに掲載されている必要があり

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

[self performSelector:@selector(myDelayedMethod) withObject: self afterDelay: desiredDelay]; 
5

: - 私の場合は、次のスケジューリング直前にコール遅れcancel dispatch_after() method?を、それは(それは本当にありません)重複として閉じられています。とにかく、これはgoogleが "dispatch_after cancel"のために返す場所なので、...

この質問はかなり基本的なものであり、私は確かに、さまざまなプラットフォーム特有のものに頼らずにランループタイマー、インスタンスに含まれたブーリアン、ヘビーブロックマジックなどがあります。 GCDは通常のCライブラリとして使用することができ、タイマーのようなものは全くないかもしれません。

幸運にも、任意の生涯スキームでディスパッチブロックを取り消す方法があります。

  1. dispatch_after(またはdispatch_async、実際は問題ではありません)に渡すブロックごとに動的ハンドルを付加する必要があります。
  2. このハンドルは、ブロックが実際に起動されるまででなければなりません。
  3. このハンドルのメモリ管理はそれほど明白ではありません。ブロックがハンドルを解放すると、後でダングリングポインターを参照解除することがありますが、解放すればブロックできます。
  4. したがって、必要に応じて所有権を渡す必要があります。
  5. 2つのブロックがあります.1つは制御ブロックですが、いずれにも発生し、2つ目はペイロードがキャンセルされる可能性があります。それだ

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例:それはすべてまで存在するので、元々、キューを保持

  • dispatch_after():ノート

    - (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. 
    } 
    

    制御ブロックが実行される。

  • ペイロードがキャンセルされた場合(またはオーナーの有効期間を過ぎた場合)、ANDブロックが実行された場合、async_handleは解放されます。
  • async_handleの動的メモリオーバーヘッドは、送信されるブロックの実際の配列を保持するdispatch_after()およびdispatch_queue_tの内部構造に比べて絶対的に軽微であり、適切なときにそれらをデキューします。
  • shouldCallとshouldFreeが実際に同じ反転フラグであることがわかります。しかし、所有者インスタンスは、ペイロードブロックを実際にキャンセルすることなく所有権を渡したり、「self」やその他の所有者関連データに依存しない場合でも、[dealloc]自体を渡すことがあります。これは、dispatch_cancel_h()への追加のshouldCallAnyway引数を使用して実装できます。
  • 警告ノート:この解決方法では、XYZフラグの同期もなく、制御ブロックとキャンセルルーチンの競合が発生する可能性があります。 OSAtomicOr32Barrier()& coを使用して同期します。
+0

注:答えのコードはARC用に書かれています。 MRCまたは純粋なCでは、早すぎる解放を解決するために、制御ブロックのdispatch_after_h()およびBlock_releaseで明示的にBlock_retain'edする必要があります。 – user3125367

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]; 
    } 
} 
7

私は約dispatch_afterhereを取り消す質問に答えました。しかし、私が解決策を見つけるためにgoogleを使うと、このスレッドにも戻ります。

iOS 8とOS X Yosemiteは、実行を開始する前にブロックをキャンセルできるようにdispatch_block_cancelを導入しました。その答えに関する詳細を表示することができますhere

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

+0

元の答えは本質的に「これをしないでください。代わりにこのようにしてください。 –

0

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

私は、モデルレイヤーがプレゼンテーションに更新されていることを確認しながら、setBeginTimeを使用すると、さまざまな時に起動する必要がある長いアニメーションのアニメーションを作成しています。適切なタイミングでレイヤーを作成するなど、私はdispatch_afterを使用し始めました。ただし、アニメーションのシリーズを再開したいときに特に気になっていました。

私は私が持っている私の-(void) startの内側その後、私のUIViewのインスタンス上CFTimeInterval startCalled;を保ち、そしてよ:

startCalled = CACurrentMediaTime(); 
CFTimeInterval thisStartCalled = startCalled; 

dispatch_afterブロックの開始時に、私は、持っている:

if (thisStartCalled != startCalled) return; 

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

1

GCD DispatchWorkItem 3 IOSの10とスウィフト以来がキャンセルされています。 ただ、作業項目にインスタンスを保持し、それがキャンセルされていないかどうかを確認し、それを取り消す:

// 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 { 
    print("Work:\(work)") 
    dispatchPrecondition(condition: .onQueue(.main)) 
    work.cancel() 
} 
関連する問題