Làm thế nào để bạn kích hoạt một khối sau một độ trễ, như -performSelector: withObject: afterDelay:?


735

Có cách nào để gọi một khối với tham số nguyên thủy sau một độ trễ, như sử dụng performSelector:withObject:afterDelay:nhưng với một đối số như int/ double/ float?


Đây là một điểm hiếm hoi mà GCD có thể làm điều gì đó NSOperation không thể?
Vô danh trắng

Câu trả lời:


1175

Tôi nghĩ rằng bạn đang tìm kiếm dispatch_after(). Nó yêu cầu khối của bạn chấp nhận không có tham số, nhưng bạn chỉ có thể để khối bắt các biến đó từ phạm vi cục bộ của bạn.

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

Thêm: https://developer.apple.com/documentation/dispatch/1452876-dispatch_after


88
Thật ra, điều đó không đúng. Các đối tượng bị bắt bởi một khối không được đánh dấu là nằm trong bộ lưu trữ __block được giữ lại bởi khối đó và được giải phóng bởi khối khi nó bị phá hủy (khi số lượng giữ lại của nó xuống 0). Đây là tài liệu về điều đó: developer.apple.com/l
Ryan

9
này dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC)đoạn là khó chịu. Không có cách nào sạch hơn cho việc này sao?
samvermette

7
Có, dispatch_get_current_queue()luôn trả về hàng đợi mà mã đang được chạy. Vì vậy, khi mã này được chạy từ luồng chính, khối cũng sẽ được thực thi trên luồng chính.
Ryan

20
dispatch_get_current_queue()hiện không được chấp nhận
Matej

9
Ngoài NSEC_PER_SEC, NSEC_PER_MSEC cũng tồn tại, trong trường hợp bạn muốn chỉ định mili giây;)
cprcrack

504

Bạn có thể sử dụng dispatch_afterđể gọi một khối sau. Trong Xcode, bắt đầu nhập dispatch_aftervà nhấn Enterđể tự động hoàn thành theo các bước sau:

nhập mô tả hình ảnh ở đây

Đây là một ví dụ với hai số float là "đối số". Bạn không cần phải dựa vào bất kỳ loại macro nào và mục đích của mã là khá rõ ràng:

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

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

Mục tiêu 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
Hãy cẩn thận thời gian trì hoãn không phải là gấp đôi. Vì vậy, đừng thử NSEC_PER_SEC * 0,5 trong nửa giây, nó sẽ không hoạt động! Bạn cần giảm xuống mili giây và sử dụng NSEC_PER_MSEC * 500. Vì vậy, bạn nên thay đổi mẫu mã của mình thành: int delayInSeconds = 2 để hiển thị mọi người không thể sử dụng phân số NSEC_PER_SEC.
malhal

11
@malhal Trên thực tế, NSEC_PER_SEC * 0.5sẽ làm việc như NSEC_PER_MSEC * 500. Mặc dù bạn đúng khi lưu ý rằng dispatch_timesẽ có số nguyên 64 bit, giá trị mà nó mong đợi là tính bằng nano giây. NSEC_PER_SECđược định nghĩa là 1000000000ullvà nhân nó với hằng số dấu phẩy động 0.5sẽ thực hiện ngầm định số học dấu phẩy động, năng suất 500000000.0, trước khi nó được truyền lại một cách rõ ràng thành số nguyên 64 bit. Vì vậy, nó hoàn toàn chấp nhận được để sử dụng một phần nhỏ NSEC_PER_SEC.
Junjie

202

Làm thế nào về việc sử dụng thư viện đoạn mã tích hợp Xcode?

nhập mô tả hình ảnh ở đây

Cập nhật cho Swift:

Nhiều phiếu bầu đã truyền cảm hứng cho tôi để cập nhật câu trả lời này.

Thư viện đoạn mã Xcode tích dispatch_afterhợp chỉ dành cho objective-cngôn ngữ. Mọi người cũng có thể tạo Đoạn mã tùy chỉnh của riêng mình cho Swift.

Viết cái này trong 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#>
    })

Kéo mã này và thả nó vào khu vực thư viện đoạn mã. nhập mô tả hình ảnh ở đây

Cuối danh sách đoạn mã, sẽ có một thực thể mới được đặt tên My Code Snippet. Chỉnh sửa này cho một tiêu đề. Đối với đề xuất khi bạn nhập Xcode điền vào Completion Shortcut.

Để biết thêm thông tin, xem CreationaCustomCodeSnippet .

Cập nhật Swift 3

Kéo mã này và thả nó vào khu vực thư viện đoạn mã.

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

19
Có ai thực sự sử dụng tính năng này trong Xcode không? Tôi thích chỉ gõ nó như cửa sổ bật lên gợi ý mã và rất dễ sử dụng.
Supertecnoboff

6
Cho đến khi biết tôi chỉ nghĩ sao chép và dán là cách dễ nhất để viết mã. Bây giờ tôi chỉ cần kéo và thả .... hahaha
arshu

58

Mở rộng câu trả lời của Jaime Cham Tôi đã tạo một danh mục NSObject + Blocks như bên dưới. Tôi cảm thấy các phương thức này phù hợp hơn với các performSelector:phương thức NSObject hiện có

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

và sử dụng như vậy:

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

5
Các delaynên có NSTimeInterval(mà là một double). #import <UIKit/UIKit.h>không cần thiết Và, tôi không thấy lý do tại sao - (void)performBlock:(void (^)())block;có thể hữu ích, vì vậy có thể được xóa khỏi tiêu đề.
ý nghĩa-vấn đề

@ ý nghĩa-vấn đề, cả hai điểm hợp lệ +1, tôi đã cập nhật câu trả lời của mình cho phù hợp.
Oliver Pearmain

Điều này không đúng chút nào, PerformanceSelector phải được xóa một cách rõ ràng trên dealloc, nếu không bạn sẽ gặp phải hành vi và sự cố thực sự kỳ lạ, chính xác hơn là sử dụng công
cụ_khi sau

21

Có lẽ đơn giản hơn là đi qua GCD, trong một lớp ở đâu đó (ví dụ "Util") hoặc Danh mục trên Đối tượng:

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

Vì vậy, để sử dụng:

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

3
@Jaimie Cham Tại sao bạn nghĩ đi qua GCD là khó khăn?
Besi

2
Đi qua GCD có hành vi hơi khác so với PerformanceSelector: afterDelay:, vì vậy có thể có lý do để không sử dụng GCD. Xem, ví dụ, câu hỏi sau: stackoverflow.com/questions/10440412/ trên
fishinear

Tại sao bạn sao chép khối trước khi chuyển nó sang PerformanceSelector?
c roald

1
Xin lỗi về sự chậm trễ. @croald: Tôi nghĩ bạn cần bản sao để di chuyển khối từ ngăn xếp sang đống.
Jaime Cham

@Besi: nhiều lời và che giấu ý định.
Jaime Cham

21

Đối với Swift, tôi đã tạo một hàm toàn cầu, không có gì đặc biệt, bằng cách sử dụng dispatch_afterphương thức. Tôi thích điều này hơn vì nó dễ đọc và dễ sử dụng:

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

Mà bạn có thể sử dụng như sau:

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

1
Tôi đề nghị trao đổi đối số và đổi tên thành after. Sau đó, bạn có thể viết:after(2.0){ print("do somthing") }
Lars Blumberg

16

Đây là 2 xu = 5 phương pháp của tôi;)

Tôi thích gói gọn các chi tiết này và yêu cầu AppCode cho tôi biết cách hoàn thành câu của mình.

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

PerformanceSelector: WithObject luôn lấy một đối tượng, vì vậy để truyền các đối số như int / double / float, v.v ..... Bạn có thể sử dụng một cái gì đó như thế này.

// NSNumber là một đối tượng ..

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



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

     [txtUsername setAlpha: [number floatValue] ];

}

Tương tự như cách bạn có thể sử dụng [NSNumber numberWithInt:] vv .... và trong phương thức nhận, bạn có thể chuyển đổi số thành định dạng của mình dưới dạng [số int] hoặc [số nhân đôi].


8

Hàm Clark_after gửi một đối tượng khối tới hàng đợi công văn sau một khoảng thời gian nhất định. Sử dụng mã dưới đây để thực hiện một số taks liên quan đến giao diện người dùng sau 2,0 giây.

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

Trong 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: {

          })

có một lỗi đánh máy: mainQueue, thay vìmainQueue)
Bastian

5

Đây là cách Swift 3 để xếp hàng làm việc sau khi trì hoãn.

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

5

Đây là một trợ giúp hữu ích để ngăn chặn cuộc gọi GCD phiền phức lặp đi lặp lại:

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

Bây giờ bạn chỉ cần trì hoãn mã của mình trên luồng chính như thế này:

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

Nếu bạn muốn trì hoãn mã của mình đến các luồng khác nhau :

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

Nếu bạn thích một Framework cũng có một số tính năng tiện dụng hơn thì hãy kiểm tra HandySwift . Bạn có thể thêm nó vào dự án của mình thông qua Carthage sau đó sử dụng chính xác như trong các ví dụ trên:

import HandySwift    

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

Điều này là ngầm định rằng hàm trì hoãn của bạn thực thi mã từ luồng nền. Ai đó sử dụng ví dụ của bạn có thể gặp khó khăn khi gỡ lỗi ứng dụng bị lỗi, nếu họ đặt bất kỳ mã nào liên quan đến giao diện người dùng vào // phần mã bị trì hoãn .
nalexn

Theo mặc định, phương thức của tôi sử dụng luồng chính để không xảy ra. Xem công văn mặc định cho .Main?
Jeehut


4

Trong swift 3, chúng ta chỉ cần sử dụng hàm DispatchQueue.main.asyncAfter để kích hoạt bất kỳ chức năng hoặc hành động nào sau thời gian trễ là 'giây'. Ở đây trong mã chúng tôi đã đặt độ trễ sau 1 giây. Bạn gọi bất kỳ chức năng nào bên trong cơ thể của chức năng này sẽ kích hoạt sau độ trễ 1 giây.

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

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

}

4

Xcode 10.2 và Swift 5 trở lên

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

1

Bạn có thể bao bọc đối số trong lớp của riêng bạn hoặc bọc cuộc gọi phương thức trong một phương thức không cần truyền vào kiểu nguyên thủy. Sau đó gọi phương thức đó sau khi bạn trì hoãn và trong phương thức đó thực hiện bộ chọn bạn muốn thực hiện.


1

Đây là cách bạn có thể kích hoạt một khối sau khi trì hoãn trong 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)
}

Nó bao gồm như là một chức năng tiêu chuẩn trong repo của tôi .


1

Swift 3 & Xcode 8.3.2

Mã này sẽ giúp bạn, tôi cũng thêm một lời giải thích

// 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

Tôi tin rằng tác giả không hỏi làm thế nào để chờ thời gian phân đoạn (độ trễ), mà thay vào đó làm thế nào để vượt qua vô hướng như đối số của bộ chọn (withObject :) và cách nhanh nhất trong mục tiêu C hiện đại là:

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

bộ chọn của bạn phải thay đổi tham số của nó thành NSNumber và truy xuất giá trị bằng cách sử dụng bộ chọn như floatValue hoặc doubleValue

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.