Bagaimana Anda memicu blok setelah penundaan, seperti -performSelector: withObject: afterDelay :?

734

Apakah ada cara untuk memanggil blok dengan parameter primitif setelah penundaan, seperti menggunakan performSelector:withObject:afterDelay:tetapi dengan argumen seperti int/ double/ float?

Egil
sumber
Ini adalah titik langka di mana GCD dapat melakukan sesuatu NSOperasi tidak bisa bukan?
Anonim Putih

Jawaban:

1175

Saya pikir Anda sedang mencari dispatch_after(). Ini membutuhkan blok Anda untuk tidak menerima parameter, tetapi Anda bisa membiarkan blok menangkap variabel-variabel itu dari lingkup lokal Anda.

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

Lebih lanjut: https://developer.apple.com/documentation/dispatch/1452876-dispatch_after

Ryan
sumber
88
Sebenarnya itu tidak benar. Objek yang ditangkap oleh blok yang tidak ditandai sebagai berada dalam __block penyimpanan dipertahankan oleh blok, dan dirilis oleh blok ketika dihancurkan (ketika jumlah penahanannya pergi ke 0). Inilah dokumentasi tentang hal itu: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Ryan
9
ini dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC)potongan jahat. Apakah tidak ada cara yang lebih bersih untuk ini?
samvermette
7
Ya, dispatch_get_current_queue()selalu mengembalikan antrean tempat kode dijalankan. Jadi ketika kode ini dijalankan dari utas utama, blok juga akan dieksekusi pada utas utama.
Ryan
20
dispatch_get_current_queue()sudah ditinggalkan sekarang
Matej
9
Selain NSEC_PER_SEC, NSEC_PER_MSEC juga ada, jika Anda ingin menentukan milidetik;)
cprcrack
504

Anda dapat menggunakan dispatch_afteruntuk memanggil blok nanti. Di Xcode, mulailah mengetik dispatch_afterdan tekan Enteruntuk lengkapi otomatis sebagai berikut:

masukkan deskripsi gambar di sini

Inilah contoh dengan dua pelampung sebagai "argumen." Anda tidak harus bergantung pada jenis makro apa pun, dan maksud kode itu cukup jelas:

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)")
}

Cepat 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)")
}

Tujuan 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);
});
Steven Hepting
sumber
45
Hati-hati waktu tunda tidak ganda. Jadi jangan coba NSEC_PER_SEC * 0,5 selama setengah detik tidak akan berhasil! Anda harus beralih ke milidetik dan menggunakan NSEC_PER_MSEC * 500. Jadi, Anda harus mengubah sampel kode Anda menjadi: int delayInSeconds = 2 untuk menunjukkan bahwa orang tidak dapat menggunakan pecahan NSEC_PER_SEC.
Malhal
11
@malhal Sebenarnya, NSEC_PER_SEC * 0.5akan bekerja sama dengan NSEC_PER_MSEC * 500. Meskipun Anda benar untuk mencatat bahwa dispatch_timemengharapkan integer 64-bit, nilai yang diharapkan dalam nanodetik. NSEC_PER_SECdidefinisikan sebagai 1000000000ull, dan mengalikannya dengan konstanta floating-point 0.5secara implisit akan melakukan aritmatika floating-point, menghasilkan 500000000.0, sebelum secara eksplisit dilemparkan kembali ke integer 64-bit. Jadi bisa diterima menggunakan sebagian kecil dari NSEC_PER_SEC.
junjie
202

Bagaimana dengan menggunakan pustaka cuplikan kode bawaan Xcode?

masukkan deskripsi gambar di sini

Pembaruan untuk Swift:

Banyak suara menginspirasi saya untuk memperbarui jawaban ini.

Pustaka cuplikan kode Xcode bawaan dispatch_afterhanya untuk objective-cbahasa. Orang juga dapat membuat Cuplikan Kode Kustom untuk Swift.

Tulis ini dalam 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#>
    })

Seret kode ini dan letakkan di area pustaka potongan kode. masukkan deskripsi gambar di sini

Di bawah daftar cuplikan kode, akan ada entitas baru bernama My Code Snippet. Edit ini untuk judul. Untuk saran saat Anda mengetik Xcode, isikan Completion Shortcut.

Untuk info lebih lanjut, lihat CreatingaCustomCodeSnippet .

Perbarui Swift 3

Seret kode ini dan letakkan di area pustaka potongan kode.

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(<#delayInSeconds#>)) {
    <#code to be executed after a specified delay#>
}
Warif Akhand Rishi
sumber
19
Adakah yang benar-benar menggunakan fitur ini di Xcode? Saya lebih suka mengetiknya sebagai saran kode popup dan mudah digunakan.
Supertecnoboff
6
Sampai tahu saya hanya berpikir copy & paste adalah cara termudah untuk kode. Sekarang saya hanya drag & drop .... hahaha
arshu
58

Memperluas jawaban Jaime Cham, saya membuat kategori + Blok NSObject seperti di bawah ini. Saya merasa metode ini lebih cocok dengan performSelector:metode NSObject yang ada

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

dan gunakan seperti ini:

[anyObject performBlock:^{
    [anotherObject doYourThings:stuff];
} afterDelay:0.15];
Oliver Pearmain
sumber
5
The delayharus dari NSTimeInterval(yang merupakan double). #import <UIKit/UIKit.h>tidak dibutuhkan. Dan, saya tidak mengerti mengapa ini - (void)performBlock:(void (^)())block;bisa berguna, jadi bisa dihapus dari header.
maknanya penting
@ arti-penting, kedua poin valid +1, saya telah memperbarui jawaban saya sesuai.
Oliver Pearmain
ini tidak benar sama sekali, performSelector harus dihapus secara eksplisit di dealloc, atau Anda akan mengalami perilaku yang sangat aneh dan crash, yang lebih benar adalah dengan menggunakan dispatch_after
Peter Lapisu
21

Mungkin lebih sederhana daripada melalui GCD, di kelas di suatu tempat (misalnya "Util"), atau Kategori di Obyek:

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

Jadi untuk digunakan:

[Util runAfterDelay:2 block:^{
    NSLog(@"two seconds later!");
}];
Jaime Cham
sumber
3
@Jaimie Cham Mengapa Anda pikir melalui GCD itu sulit?
Besi
2
Melewati GCD memang memiliki perilaku yang sedikit berbeda dari PerformSelector: afterDelay :, jadi mungkin ada alasan untuk tidak menggunakan GCD. Lihat, misalnya, pertanyaan berikut: stackoverflow.com/questions/10440412/…
fishinear
Mengapa Anda menyalin blok sebelum meneruskannya ke performSelector?
c roald
1
Maaf atas keterlambatannya. @croald: Saya pikir Anda memerlukan salinan untuk memindahkan blok dari tumpukan ke tumpukan.
Jaime Cham
@Besi: lebih banyak bertele-tele dan menyembunyikan niat.
Jaime Cham
21

Untuk Swift, saya telah membuat fungsi global, tidak ada yang istimewa, menggunakan dispatch_aftermetode ini. Saya lebih suka ini karena mudah dibaca dan mudah digunakan:

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)
}

Yang bisa Anda gunakan sebagai berikut:

performBlock({ () -> Void in
    // Perform actions
}, afterDelay: 0.3)
Antoine
sumber
1
Saya sarankan untuk bertukar argumen dan mengganti namanya menjadi after. Maka Anda dapat menulis:after(2.0){ print("do somthing") }
Lars Blumberg
16

Inilah 2 sen saya = 5 metode;)

Saya suka merangkum detail-detail ini dan minta AppCode memberi tahu saya cara menyelesaikan kalimat saya.

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);
}
Dan Rosenstark
sumber
8

PerformSelector: WithObject selalu mengambil objek, jadi untuk memberikan argumen seperti int / double / float dll ..... Anda dapat menggunakan sesuatu seperti ini.

// NSNumber adalah objek ..

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



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

     [txtUsername setAlpha: [number floatValue] ];

}

Dengan cara yang sama Anda dapat menggunakan [NSNumber numberWithInt:] dll .... dan dalam metode penerima Anda dapat mengonversi angka ke dalam format Anda sebagai [number int] atau [number double].

Agustinus
sumber
8

Fungsi dispatch_after mengirimkan objek blok ke antrian pengiriman setelah periode waktu tertentu. Gunakan kode di bawah ini untuk melakukan beberapa pengambilan terkait UI setelah 2,0 detik.

            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")
            })

Di cepat 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: {

          })
Himanshu Mahajan
sumber
ada kesalahan ketik: mainQueue, bukannyamainQueue)
Bastian
5

Inilah cara Swift 3 untuk mengantri kerja setelah penundaan.

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

Berikut ini adalah bantuan praktis untuk mencegah panggilan GCD yang menjengkelkan berulang kali:

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)
        }
    }
}

Sekarang Anda cukup menunda kode Anda di utas utama seperti ini:

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

Jika Anda ingin menunda kode Anda ke utas berbeda :

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

Jika Anda lebih suka Kerangka yang juga memiliki beberapa fitur yang lebih berguna maka checkout HandySwift . Anda dapat menambahkannya ke proyek Anda melalui Carthage lalu menggunakannya persis seperti pada contoh di atas:

import HandySwift    

delay(bySeconds: 1.5) { 
    // delayed code
}
Astaga
sumber
Ini secara implisit bahwa fungsi penundaan Anda mengeksekusi kode dari utas latar belakang. Seseorang yang menggunakan contoh Anda dapat benar-benar kesulitan men-debug aplikasi yang sedang mogok, jika mereka memasukkan kode terkait UI di dalam // bagian kode tertunda .
nalexn
Secara default metode saya menggunakan utas utama sehingga seharusnya tidak terjadi. Lihat dispatchLevel default ke .Main?
Jeehut
4

Ada yang bagus dalam framework BlocksKit.

BlocksKit

(dan kelas)

BBlocksKit.m

psy
sumber
4

Di swift 3, Kita cukup menggunakan fungsi DispatchQueue.main.asyncAfter untuk memicu fungsi atau tindakan apa pun setelah penundaan 'n' detik. Di sini, dalam kode, kami telah menetapkan penundaan setelah 1 detik. Anda memanggil fungsi apa pun di dalam tubuh fungsi ini yang akan memicu setelah penundaan 1 detik.

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

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

}
Rouny
sumber
4

Xcode 10.2 dan Swift 5 ke atas

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

Anda bisa membungkus argumen di kelas Anda sendiri, atau membungkus pemanggilan metode dalam metode yang tidak perlu diteruskan dalam tipe primitif. Kemudian panggil metode itu setelah penundaan Anda, dan di dalam metode itu lakukan pemilih yang ingin Anda lakukan.

GendoIkari
sumber
1

Inilah cara Anda dapat memicu blok setelah penundaan di 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)
}

Termasuk sebagai fungsi standar dalam repo saya .

Esqarrouth
sumber
1

Swift 3 & Xcode 8.3.2

Kode ini akan membantu Anda, saya juga menambahkan penjelasan

// 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 :)
        }
    }   
}
Andrian Rahardja
sumber
0

Saya percaya penulis tidak bertanya bagaimana menunggu waktu fraksional (penundaan), melainkan bagaimana cara melewatkan skalar sebagai argumen pemilih (withObject :) dan cara tercepat dalam tujuan modern C adalah:

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

pemilih Anda harus mengubah parameternya ke NSNumber, dan mengambil nilainya menggunakan pemilih seperti floatValue atau doubleValue

André
sumber