ทำบางสิ่งทุกๆ x นาทีใน Swift


113

ฉันจะเรียกใช้ฟังก์ชันทุกนาทีได้อย่างไร? ใน JavaScript ฉันสามารถทำสิ่งต่างๆเช่นsetIntervalมีสิ่งที่คล้ายกันใน Swift หรือไม่

ผลลัพธ์ที่ต้องการ:

Hello World นาทีละครั้ง ...


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

คำตอบ:


171
var helloWorldTimer = NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: Selector("sayHello"), userInfo: nil, repeats: true)

func sayHello() 
{
    NSLog("hello World")
}

อย่าลืมนำเข้า Foundation

Swift 4:

 var helloWorldTimer = Timer.scheduledTimer(timeInterval: 60.0, target: self, selector: #selector(ViewController.sayHello), userInfo: nil, repeats: true)

 @objc func sayHello() 
 {
     NSLog("hello World")
 }

1
แต่ถ้าคุณต้องการสลับระหว่างมุมมองล่ะ? รหัสจะหยุดใช่ไหม
Cing

5
@Cing ขึ้นอยู่กับสิ่งที่ตัวเองอ้างถึง
Antzi

1
อย่าลืมว่าNSTimerยังคงมีเป้าหมายของมันดังนั้นกับการตั้งค่านี้ถ้าhelloWorldTimerเป็นทรัพย์สินบนselfคุณได้มีตัวเองรักษาวงจรที่selfยังคงรักษาhelloWorldTimerและยังคงรักษาhelloWorldTimer self
MANIAK_dobrii

@MANIAK_dobrii คุณสามารถแสดงความคิดเห็นเกี่ยวกับวิธีการทำลายวงจรการรักษาได้หรือไม่? หาก VC ถูกปิด แต่ตัวจับเวลาไม่ถูกยกเลิกวงจรยังคงอยู่ในชั้นเชิง? ดังนั้นคุณต้องทั้งยกเลิกตัวจับเวลาแล้วปิดมุมมองเพื่อตัดวงจร?
Dave G

1
สำหรับ Swift 4 วิธีที่คุณต้องการรับตัวเลือกจะต้องสัมผัสกับ Objective-C ดังนั้นต้องเพิ่มแอตทริบิวต์ @objc ในการประกาศวิธีการ เช่น `` @objc func sayHello () {} ``
Shamim Hossain

136

หากกำหนดเป้าหมาย iOS เวอร์ชัน 10 ขึ้นไปคุณสามารถใช้การแสดงผลแบบบล็อกTimerซึ่งช่วยลดความซับซ้อนของวงจรอ้างอิงที่อาจเกิดขึ้นได้เช่น:

weak var timer: Timer?

func startTimer() {
    timer?.invalidate()   // just in case you had existing `Timer`, `invalidate` it before we lose our reference to it
    timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in
        // do something here
    }
}

func stopTimer() {
    timer?.invalidate()
}

// if appropriate, make sure to stop your timer in `deinit`

deinit {
    stopTimer()
}

แม้ว่าTimerโดยทั่วไปจะดีที่สุด แต่เพื่อความสมบูรณ์ แต่ฉันควรทราบว่าคุณสามารถใช้ตัวจับเวลาการจัดส่งซึ่งมีประโยชน์สำหรับการตั้งเวลาตัวจับเวลาบนเธรดพื้นหลัง ด้วยตัวจับเวลาการจัดส่งเนื่องจากเป็นแบบบล็อกจึงหลีกเลี่ยงความท้าทายของวัฏจักรการอ้างอิงที่มีรูปแบบเก่าtarget/ selectorรูปแบบTimerตราบเท่าที่คุณใช้weakการอ้างอิง

ดังนั้น:

var timer: DispatchSourceTimer?

func startTimer() {
    let queue = DispatchQueue(label: "com.domain.app.timer")  // you can also use `DispatchQueue.main`, if you want
    timer = DispatchSource.makeTimerSource(queue: queue)
    timer!.schedule(deadline: .now(), repeating: .seconds(60))
    timer!.setEventHandler { [weak self] in
        // do whatever you want here
    }
    timer!.resume()
}

func stopTimer() {
    timer?.cancel()
    timer = nil
}

deinit {
    self.stopTimer()
}

สำหรับข้อมูลเพิ่มเติมโปรดดูการสร้างตัวจับเวลาของตัวอย่างแหล่งที่มาของการจัดส่งในส่วนแหล่งที่มาของการจัดส่งของคู่มือการเขียนโปรแกรมพร้อมกัน


สำหรับ Swift 2 โปรดดู การแก้ไขก่อนหน้าของคำตอบนี้


คุณจะใช้ตัวจับเวลาที่ดำเนินการเพียงครั้งเดียวภายในแนวทางนี้ได้อย่างไร
Juan Boero

@JuanPabloBoero - ฉันจะไม่ใช้วิธีนี้กับสถานการณ์ไฟไหม้ครั้งเดียว dispatch_afterคุณต้องการใช้ NSTimerหรือไม่ใช่การทำซ้ำ
Rob

@Rob ฉันมีป้ายตัวนับบนหน้าจอซึ่งได้รับการอัปเดตทุกวินาทีจากฟังก์ชันและฉันใช้รหัสด้านบนเพื่อเพิ่มตัวนับและอัปเดตข้อความป้ายกำกับ แต่ปัญหาที่ฉันพบคือตัวแปรตัวนับของฉันได้รับการอัปเดตทุกวินาที แต่หน้าจอไม่แสดงค่าตัวนับล่าสุดใน UILabel ของฉัน
Nagendra Rao

Rao ข้างต้นมีประโยชน์สำหรับการดำเนินการบางอย่างกับเธรดพื้นหลัง แต่การอัปเดต UI ของคุณจะต้องเกิดขึ้นในคิวหลัก ดังนั้นกำหนดเวลานี้ในเธรดหลักหรืออย่างน้อยก็ส่งการอัปเดต UI กลับไปที่เธรดหลัก
Rob

1
คำถามนี้ไม่ได้อยู่ที่นี่ในความคิดเห็นของคำตอบนี้ ลบความคิดเห็นของคุณด้านบนและโพสต์คำถามของคุณเอง แต่ในระยะสั้นโดยทั่วไปคุณไม่ได้ให้แอปทำงานในพื้นหลัง แต่เพียงทำให้ดูเหมือนว่าเป็น ดูstackoverflow.com/a/31642036/1271826
Rob

17

นี่คือการอัปเดตสำหรับNSTimerคำตอบสำหรับ Swift 3 (ซึ่งNSTimerถูกเปลี่ยนชื่อเป็นTimer) โดยใช้การปิดแทนที่จะเป็นฟังก์ชันที่มีชื่อ:

var timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
    (_) in
    print("Hello world")
}

6
สำหรับ iOS 10 เท่านั้น
Glenn

โปรดอย่าโพสต์โซลูชัน ios 10+
Numan Karaaslan

13

หากคุณสามารถปล่อยให้ล่องลอยได้สักระยะหนึ่งนี่เป็นวิธีง่ายๆที่เรียกใช้โค้ดบางส่วนทุกนาที:

private func executeRepeatedly() {
    // put your code here

    DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) { [weak self] in
        self?.executeRepeatedly()
    }
}

เพียงเรียกใช้executeRepeatedly()ครั้งเดียวและจะดำเนินการทุกนาที การดำเนินการจะหยุดลงเมื่อปล่อยอ็อบเจ็กต์ที่เป็นเจ้าของ ( self) คุณยังสามารถใช้แฟล็กเพื่อระบุว่าการดำเนินการต้องหยุดลง


การตัดสินใจนี้สะดวกกว่าการใช้ตัวจับเวลาทั้งหมดนี้เพิ่มลงใน runloops ทำให้ไม่ถูกต้อง ... เจ๋ง!
julia_v

นี่ดูเหมือนวิธีแก้ปัญหาที่ถูกต้อง แต่กำลังสร้างวงจรการเก็บรักษาเมื่อฉันใช้กับบริการเว็บของฉัน ...
jayant rawat

11

คุณสามารถใช้Timer(รวดเร็ว 3)

var timer = Timer.scheduledTimerWithTimeInterval(60, target: self, selector: Selector("function"), userInfo: nil, repeats: true)

ในตัวเลือก () คุณใส่ชื่อฟังก์ชันของคุณ


คุณจะทำได้อย่างไรใน swift 3?
bibscy

1
ใช้ Timer ... NSTimerถูกเปลี่ยนชื่อ
โจเซฟ

7

ใน Swift 3.0 GCD ได้รับการปรับโครงสร้างใหม่:

let timer : DispatchSourceTimer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)

timer.scheduleRepeating(deadline: .now(), interval: .seconds(60))
timer.setEventHandler
{
    NSLog("Hello World")
}
timer.resume()

สิ่งนี้มีประโยชน์อย่างยิ่งเมื่อคุณต้องการจัดส่งในคิวใดคิวหนึ่ง นอกจากนี้หากคุณวางแผนที่จะใช้สิ่งนี้สำหรับการอัปเดตอินเทอร์เฟซผู้ใช้ฉันขอแนะนำให้ดูCADisplayLinkเนื่องจากมันซิงโครไนซ์กับอัตราการรีเฟรช GPU

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