คุณจะเรียกบล็อกหลังจากหน่วงเวลาเช่น -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 นั้นจะถูกเก็บรักษาไว้โดยบล็อกและถูกปล่อยออกมาจากบล็อกเมื่อมันถูกทำลาย นี่คือเอกสารเกี่ยวกับที่: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Ryan

9
นี้dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC)ข้อมูลโค้ดเป็นที่น่ารังเกียจ มีวิธีที่สะอาดกว่านี้หรือไม่?
samvermette

7
ใช่dispatch_get_current_queue()ส่งคืนคิวที่โค้ดกำลังรันอยู่เสมอ ดังนั้นเมื่อเรียกใช้รหัสนี้จากเธรดหลักบล็อกจะถูกดำเนินการบนเธรดหลัก
ไรอัน

20
dispatch_get_current_queue()เลิกใช้แล้วในตอนนี้
Matej

9
นอกจาก NSEC_PER_SEC แล้ว NSEC_PER_MSEC ก็มีอยู่เช่นกันในกรณีที่คุณต้องการระบุมิลลิวินาที;)
cprcrack

504

คุณสามารถใช้dispatch_afterโทรหาบล็อกได้ในภายหลัง ใน Xcode เริ่มพิมพ์dispatch_afterและกดEnterเพื่อเติมข้อความอัตโนมัติต่อไปนี้:

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือตัวอย่างที่มีสองการลอยเป็น "อาร์กิวเมนต์" คุณไม่จำเป็นต้องพึ่งพามาโครประเภทใด ๆ และเจตนาของโค้ดนั้นค่อนข้างชัดเจน:

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
ระวังเวลาหน่วงไม่เป็นสองเท่า ดังนั้นอย่าลอง NSEC_PER_SEC * 0.5 นานครึ่งวินาทีมันจะไม่ทำงาน! คุณต้องเลื่อนไปที่มิลลิวินาทีและใช้ NSEC_PER_MSEC * 500 ดังนั้นคุณควรเปลี่ยนตัวอย่างโค้ดของคุณเป็น: int delayInSeconds = 2 เพื่อแสดงให้ผู้อื่นไม่สามารถใช้เศษส่วนของ NSEC_PER_SEC
malhal

11
@malhal จริงจะทำงานเช่นเดียวกับNSEC_PER_SEC * 0.5 NSEC_PER_MSEC * 500ในขณะที่คุณถูกต้องที่จะทราบว่าdispatch_timeคาดว่าจะเป็นจำนวนเต็ม 64 บิตค่าที่คาดว่าจะอยู่ในหน่วยนาโนวินาที NSEC_PER_SECถูกกำหนดให้เป็น1000000000ullและคูณด้วยค่าคงที่จุดลอยตัว0.5โดยปริยายจะทำการคำนวณเลขทศนิยมโดยปริยายยอม500000000.0ก่อนที่มันจะถูกส่งกลับอย่างชัดเจนเป็นจำนวนเต็ม 64 บิต NSEC_PER_SECดังนั้นจึงเป็นที่ยอมรับอย่างสมบูรณ์ที่จะใช้ส่วนของ
junjie

202

วิธีการเกี่ยวกับการใช้โค้ดตัวอย่างโค้ดในตัวของ Xcode?

ป้อนคำอธิบายรูปภาพที่นี่

อัปเดตสำหรับ Swift:

การโหวตหลายครั้งเป็นแรงบันดาลใจให้ฉันอัปเดตคำตอบนี้

โค้ดตัวอย่างโค้ด Xcode ในตัวมีdispatch_afterเฉพาะobjective-cภาษาเท่านั้น ผู้คนยังสามารถสร้างCustom Code Snippetของ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
ฉันรู้ว่าเพิ่งคิดว่าการคัดลอกและวางเป็นวิธีที่ง่ายที่สุดในการเขียนโค้ด ตอนนี้ฉันแค่ลากและวาง .... ฮ่าฮ่าฮ่า
arshu

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;อาจเป็นประโยชน์ดังนั้นสามารถลบออกจากส่วนหัวได้
ความหมายมีความสำคัญ

@ ความหมาย - สำคัญทั้งคะแนนที่ถูกต้อง +1 ฉันได้อัปเดตคำตอบของฉันแล้ว
Oliver Pearmain

สิ่งนี้ไม่ถูกต้องเลย performSelector จะต้องถูกลบออกอย่างชัดเจนใน dealloc มิฉะนั้นคุณจะพบกับพฤติกรรมที่ผิดปกติและล้มเหลวอย่างแท้จริงยิ่งถูกต้องมากขึ้นคือการใช้ dispatch_after
Peter Lapisu

21

อาจง่ายกว่าการไปถึง GCD ในชั้นเรียนที่ใดที่หนึ่ง (เช่น "Util") หรือหมวดหมู่บนวัตถุ:

+ (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 นั้นยาก
เบซิ

2
การผ่าน GCD จะมีพฤติกรรมแตกต่างจาก PerformSelector: afterDelay: ดังนั้นอาจมีเหตุผลที่จะไม่ใช้ GCD ดูตัวอย่างคำถามต่อไปนี้: stackoverflow.com/questions/10440412/…
fishinear

ทำไมคุณคัดลอกบล็อกก่อนส่งต่อไปยัง selector?
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") }
Lars 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:] ฯลฯ ... และในวิธีการรับคุณสามารถแปลงตัวเลขให้อยู่ในรูปแบบของคุณเป็น [number int] หรือ [number double]


8

ฟังก์ชั่น dispatch_after ส่งวัตถุบล็อกไปยังคิวการจัดส่งหลังจากระยะเวลาที่กำหนด ใช้รหัสด้านล่างเพื่อดำเนินการกับ taks ที่เกี่ยวข้องกับ UI หลังจาก 2.0 วินาที

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

ในสวิฟท์ 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)
Bastian

5

นี่คือวิธีที่รวดเร็วในการคิวงานหลังจากล่าช้า

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

ตามค่าเริ่มต้นวิธีการของฉันใช้เธรดหลักเพื่อไม่ให้เกิดขึ้น ดู dispatchLevel การตั้งค่าเริ่มต้นเป็น. Main?
Jeehut


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

}


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 :) และวิธีที่เร็วที่สุดในวัตถุประสงค์ C สมัยใหม่คือ:

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

ตัวเลือกของคุณต้องเปลี่ยนพารามิเตอร์เป็น NSNumber และดึงค่าโดยใช้ตัวเลือกเช่น floatValue หรือ doubleValue

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.