-performSelector:withObject:afterDelay:のように、遅延後にブロックをトリガーするにはどうすればよいですか?


735

使用するように、遅延の後にプリミティブのパラメータを持つブロックを呼び出すための方法があるperformSelector:withObject:afterDelay:が、のような引数では、int/ double/ float


これは、GCDがNSOperationでできないことを実行できるまれなポイントではありませんか。
匿名のホワイト

回答:


1175

あなたが探していると思いますdispatch_after()。ブロックがパラメーターを受け入れないようにする必要がありますが、代わりにローカルスコープからこれらの変数をブロックにキャプチャさせることができます。

int parameter1 = 12;
float parameter2 = 144.1;

// Delay execution of my block for 10 seconds.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    NSLog(@"parameter1: %d parameter2: %f", parameter1, parameter2);
});

詳細:https : //developer.apple.com/documentation/dispatch/1452876-dispatch_after


88
実際、そうではありません。__blockストレージにあるとマークされていないブロックによってキャプチャされたオブジェクトは、ブロックによって保持され、ブロックが破棄されるとき(保持カウントが0になるとき)に解放されます。ここではその上のドキュメントがあります:developer.apple.com/library/mac/documentation/Cocoa/Conceptual/...
ライアン

9
このdispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC)スニペットは厄介です。これのためのよりきれいな方法はありませんか?
samvermette

7
はい、dispatch_get_current_queue()常にコードが実行されているキューを返します。したがって、このコードがメインスレッドから実行されると、ブロックはメインスレッドでも実行されます。
ライアン

20
dispatch_get_current_queue()は現在非推奨です
Matej 2013

9
ミリ秒を指定したい場合に備えて、NSEC_PER_SECの他にNSEC_PER_MSECも存在します;)
cprcrack

504

を使用dispatch_afterして、後でブロックを呼び出すことができます。Xcodeで入力dispatch_afterを開始し、ヒットEnterして次のようにオートコンプリートします。

ここに画像の説明を入力してください

以下は、「引数」として2つの浮動小数点数を使用した例です。どのタイプのマクロにも依存する必要はなく、コードの意図は非常に明確です。

Swift 3、Swift 4

let time1 = 8.23
let time2 = 3.42

// Delay 2 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    print("Sum of times: \(time1 + time2)")
}

スウィフト2

let time1 = 8.23
let time2 = 3.42

// Delay 2 seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
        println("Sum of times: \(time1 + time2)")
}

目的C

CGFloat time1 = 3.49;
CGFloat time2 = 8.13;

// Delay 2 seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    CGFloat newTime = time1 + time2;
    NSLog(@"New time: %f", newTime);
});

45
遅延時間が2倍にならないように注意してください。したがって、NSEC_PER_SEC * 0.5を0.5秒間試行しないでください。機能しません。ミリ秒に落とし、NSEC_PER_MSEC * 500を使用する必要があります。したがって、コードサンプルを次のように変更する必要があります。intdelayInSeconds = 2 NSEC_PER_SECの一部を使用できないことを示す。
Malhal 2013年

11
@malhal実際にNSEC_PER_SEC * 0.5は、と同じように機能しNSEC_PER_MSEC * 500ます。がdispatch_time64ビット整数を期待していることに注意するのは正しいですが、期待する値はナノ秒単位です。NSEC_PER_SECはとして定義され1000000000ull、これに浮動小数点定数を乗算すると、明示的に64ビット整数にキャストバックされる前に0.5、暗黙的に浮動小数点演算が実行500000000.0され、が生成されます。したがって、の一部を使用することは完全に許容されますNSEC_PER_SEC
junjie 2017

202

Xcode組み込みコードスニペットライブラリを使用するのはどうですか?

ここに画像の説明を入力してください

Swiftの更新:

多くの賛成票がこの回答を更新するように促しました。

ビルトインのXcodeコードスニペットライブラリはdispatch_afterobjective-c言語にのみ対応しています。独自のカスタムコードスニペットを作成することもできますSwift

これをXcodeで記述します。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(<#delayInSeconds#> * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), {
        <#code to be executed after a specified delay#>
    })

このコードをドラッグして、コードスニペットライブラリ領域にドロップします。 ここに画像の説明を入力してください

コードスニペットリストの下部に、という名前の新しいエンティティがありMy Code Snippetます。タイトル用にこれを編集します。Xcodeに入力する際の提案として、に入力しますCompletion Shortcut

詳細については、CreatingaCustomCodeSnippetを参照してください。

Swift 3を更新する

このコードをドラッグして、コードスニペットライブラリ領域にドロップします。

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(<#delayInSeconds#>)) {
    <#code to be executed after a specified delay#>
}

19
誰かが実際にこの機能をXcodeで使用していますか?私は、コード候補ポップアップとしてそれを入力することを好み、同じくらい使いやすいです。
Supertecnoboff

6
私が知るまでは、コピーと貼り付けがコーディングの最も簡単な方法だと思っていました。今、私はドラッグアンドドロップするだけです... haha​​ha
arshu 2016年

58

Jaime Chamの答えを拡張して、以下のようにNSObject + Blocksカテゴリを作成しました。これらのメソッドは既存のperformSelector:NSObjectメソッドとよりよく一致していると感じました

NSObject + Blocks.h

#import <Foundation/Foundation.h>

@interface NSObject (Blocks)

- (void)performBlock:(void (^)())block afterDelay:(NSTimeInterval)delay;

@end

NSObject + Blocks.m

#import "NSObject+Blocks.h"

@implementation NSObject (Blocks)

- (void)performBlock:(void (^)())block
{
    block();
}

- (void)performBlock:(void (^)())block afterDelay:(NSTimeInterval)delay
{
    void (^block_)() = [block copy]; // autorelease this if you're not using ARC
    [self performSelector:@selector(performBlock:) withObject:block_ afterDelay:delay];
}

@end

次のように使用します:

[anyObject performBlock:^{
    [anotherObject doYourThings:stuff];
} afterDelay:0.15];

5
delayでなければならないNSTimeInterval(ありますdouble)。#import <UIKit/UIKit.h>必要ありません。そして、なぜ- (void)performBlock:(void (^)())block;役立つのかはわかりません。ヘッダーから削除できます。
意味の問題

@ meaning-matters、両方の有効ポイント+1、それに応じて回答を更新しました。
Oliver Pearmain 2013年

これは、より正確には、dispatch_after使用することで、performSelectorはにdeallocで明示的に削除する必要があり、さもなければ、あなたは本当に奇妙な行動やクラッシュに実行するすべての正しくない
ピーターLapisu

21

おそらく、どこかでクラス(たとえば、「Util」)、またはオブジェクトのカテゴリでGCDを通過するよりも簡単です。

+ (void)runBlock:(void (^)())block
{
    block();
}
+ (void)runAfterDelay:(CGFloat)delay block:(void (^)())block 
{
    void (^block_)() = [[block copy] autorelease];
    [self performSelector:@selector(runBlock:) withObject:block_ afterDelay:delay];
}

したがって、使用するには:

[Util runAfterDelay:2 block:^{
    NSLog(@"two seconds later!");
}];

3
@Jaimie ChamなぜGCDを通過するのが難しいと思いますか?
Besi

2
GCDの処理は、PerformSelector:afterDelay:とは少し異なる動作をするため、GCDを使用しない理由がある場合があります。:例えば、次の質問を参照してくださいstackoverflow.com/questions/10440412/...
fishinear

ブロックをperformSelectorに渡す前にコピーするのはなぜですか?
c roald

1
遅れて申し訳ありません。@croald:ブロックをスタックからヒープに移動するにはコピーが必要だと思います。
Jaime Cham

@Besi:より冗長で意図を隠します。
Jaime Cham

21

Swiftの場合、dispatch_afterメソッドを使用して、特別なものは何もないグローバル関数を作成しました。読みやすく使いやすいので、私はこれがもっと好きです:

func performBlock(block:() -> Void, afterDelay delay:NSTimeInterval){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), block)
}

次のように使用できます:

performBlock({ () -> Void in
    // Perform actions
}, afterDelay: 0.3)

1
引数を入れ替えて名前をに変更することをお勧めしますafter。次に、あなたが書くことができます:after(2.0){ print("do somthing") }
ラースBlumbergの

16

これが私の2セント= 5つのメソッドです;)

これらの詳細をカプセル化し、AppCodeに文章の完成方法を教えてもらうのが好きです。

void dispatch_after_delay(float delayInSeconds, dispatch_queue_t queue, dispatch_block_t block) {
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, queue, block);
}

void dispatch_after_delay_on_main_queue(float delayInSeconds, dispatch_block_t block) {
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_after_delay(delayInSeconds, queue, block);
}

void dispatch_async_on_high_priority_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}

void dispatch_async_on_background_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), block);
}

void dispatch_async_on_main_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_main_queue(), block);
}

8

PerformSelector:WithObjectは常にオブジェクトを取得するため、int / double / floatなどの引数を渡すために、次のように使用できます。

// NSNumberはオブジェクトです。

[self performSelector:@selector(setUserAlphaNumber:)
     withObject: [NSNumber numberWithFloat: 1.0f]       
     afterDelay:1.5];



-(void) setUserAlphaNumber: (NSNumber*) number{

     [txtUsername setAlpha: [number floatValue] ];

}

同じように[NSNumber numberWithInt:]などを使用できます。受信メソッドでは、数値を[number int]または[number double]の形式に変換できます。


8

dispatch_after関数は、一定の時間が経過すると、ブロックオブジェクトをディスパッチキューにディスパッチします。以下のコードを使用して、2.0秒後にいくつかのUI関連のタスクを実行します。

            let delay = 2.0
            let delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
            let mainQueue = dispatch_get_main_queue()

            dispatch_after(delayInNanoSeconds, mainQueue, {

                print("Some UI related task after delay")
            })

Swift 3.0では:

            let dispatchTime: DispatchTime = DispatchTime.now() + Double(Int64(2.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
            DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: {

          })

タイプミスがあります。mainQueue, 代わりにmainQueue)
バスティアン

5

遅延後に作業をキューに入れるSwift 3の方法を次に示します。

DispatchQueue.main.asyncAfter(
  DispatchTime.now() + DispatchTimeInterval.seconds(2)) {
    // do work
}

5

煩わしいGCD呼び出しを何度も繰り返さないようにするための便利なヘルパーを次に示します

public func delay(bySeconds seconds: Double, dispatchLevel: DispatchLevel = .main, closure: @escaping () -> Void) {
    let dispatchTime = DispatchTime.now() + seconds
    dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
}

public enum DispatchLevel {
    case main, userInteractive, userInitiated, utility, background
    var dispatchQueue: DispatchQueue {
        switch self {
        case .main:                 return DispatchQueue.main
        case .userInteractive:      return DispatchQueue.global(qos: .userInteractive)
        case .userInitiated:        return DispatchQueue.global(qos: .userInitiated)
        case .utility:              return DispatchQueue.global(qos: .utility)
        case .background:           return DispatchQueue.global(qos: .background)
        }
    }
}

次のように、メインスレッドでコードを遅延させるだけです。

delay(bySeconds: 1.5) { 
    // delayed code
}

コードを別のスレッド遅らせたい場合:

delay(bySeconds: 1.5, dispatchLevel: .background) { 
    // delayed code that will run on background thread
}

さらに便利な機能を備えたフレームワークを好む場合は、HandySwiftをチェックしてくださいCarthage介してプロジェクトに追加し、上記の例とまったく同じように使用できます。

import HandySwift    

delay(bySeconds: 1.5) { 
    // delayed code
}

これは、遅延関数がバックグラウンドスレッドからコードを実行することを暗黙的に示しています。あなたの例を使用している誰かが、UI-関連コードを//遅延コードセクション内に配置した場合、クラッシュするアプリのデバッグに本当に苦労する可能性があります。
nalexn 2016

デフォルトでは、私のメソッドはメインスレッドを使用するため、このようなことは起こりません。dispatchLevelがデフォルトで.Mainになっているのを確認してください。
Jeehut 2016


4

Swift 3では、DispatchQueue.main.asyncAfter関数を使用して、「n」秒の遅延後に関数またはアクションをトリガーできます。ここでは、1秒後に遅延を設定しています。この関数の本体内で、1秒の遅延後にトリガーされる任意の関数を呼び出します。

let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when) {

    // Trigger the function/action after the delay of 1Sec

}

4

Xcode 10.2およびSwift 5以降

DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
   // code to execute                 
})

1

引数を独自のクラスでラップするか、プリミティブ型で渡す必要のないメソッドでメソッド呼び出しをラップすることができます。次に、遅延後にそのメソッドを呼び出し、そのメソッド内で、実行するセレクターを実行します。


1

Swiftの遅延後にブロックをトリガーする方法は次のとおりです。

runThisAfterDelay(seconds: 2) { () -> () in
    print("Prints this 2 seconds later in main queue")
}

/// EZSwiftExtensions
func runThisAfterDelay(seconds seconds: Double, after: () -> ()) {
    let time = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))
    dispatch_after(time, dispatch_get_main_queue(), after)
}

それは私のリポジトリの標準機能として含まれています。


1

Swift 3およびXcode 8.3.2

このコードはあなたを助けるでしょう、私も説明を追加します

// Create custom class, this will make your life easier
class CustomDelay {

    static let cd = CustomDelay()

    // This is your custom delay function
    func runAfterDelay(_ delay:Double, closure:@escaping ()->()) {
        let when = DispatchTime.now() + delay
        DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
    }
}


// here how to use it (Example 1)
class YourViewController: UIViewController {

    // example delay time 2 second
    let delayTime = 2.0

    override func viewDidLoad() {
        super.viewDidLoad()

        CustomDelay.cd.runAfterDelay(delayTime) {
            // This func will run after 2 second
            // Update your UI here, u don't need to worry to bring this to the main thread because your CustomDelay already make this to main thread automatically :)
            self.runFunc()
        }
    }

    // example function 1
    func runFunc() {
        // do your method 1 here
    }
}

// here how to use it (Example 2)
class YourSecondViewController: UIViewController {

    // let say you want to user run function shoot after 3 second they tap a button

    // Create a button (This is programatically, you can create with storyboard too)
    let shootButton: UIButton = {
        let button = UIButton(type: .system)
        button.frame = CGRect(x: 15, y: 15, width: 40, height: 40) // Customize where do you want to put your button inside your ui
        button.setTitle("Shoot", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        // create an action selector when user tap shoot button
        shootButton.addTarget(self, action: #selector(shoot), for: .touchUpInside)   
    }

    // example shoot function
    func shoot() {
        // example delay time 3 second then shoot
        let delayTime = 3.0

        // delay a shoot after 3 second
        CustomDelay.cd.runAfterDelay(delayTime) {
            // your shoot method here
            // Update your UI here, u don't need to worry to bring this to the main thread because your CustomDelay already make this to main thread automatically :)
        }
    }   
}

0

私は作者が小数の時間(遅延)を待つ方法を尋ねているのではなく、セレクター(withObject :)の引数としてスカラーを渡す方法を尋ねていると思います。

[obj performSelector:...  withObject:@(0.123123123) afterDelay:10]

セレクターはパラメーターをNSNumberに変更し、floatValueやdoubleValueなどのセレクターを使用して値を取得する必要があります。

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