RunLoop ใน iOS: คืออะไร โหมดการทำงาน และวงจรเหตุการณ์

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-03-16 เวลาอ่าน: 11 นาที

RunLoop — วงจรประมวลผลเหตุการณ์ใน iOS ที่ implement โดยออบเจ็กต์ CFRunLoop (Core Foundation) และ NSRunLoop (Foundation) เป็นกลไกที่รอเหตุการณ์ (การสัมผัส timer แหล่งข้อมูลนำเข้า การแจ้งเตือน) และส่งต่อไปยังตัวจัดการที่เกี่ยวข้องบนเธรด แต่ละเธรดใน iOS มี RunLoop ได้สูงสุดหนึ่งตัว แต่จะถูกสร้างโดยอัตโนมัติเฉพาะสำหรับ Main Thread เท่านั้น ตาม Apple CFRunLoop Documentation RunLoop มีความสำคัญต่อการทำงานของ timer แอนิเมชัน และการตรวจสอบแหล่งที่มาในเธรดพื้นหลัง

ประเด็นสำคัญ

  • RunLoop — event loop ที่ประมวลผลเหตุการณ์บนเธรด: การสัมผัส timer แหล่งข้อมูลนำเข้า
  • Main Thread มี RunLoop อัตโนมัติ (CFRunLoopGetMain()) เธรดพื้นหลัง — ต้องเริ่มต้นด้วยตนเอง
  • สามโหมด: .default (หลัก), .tracking (สกรอลล์), .common (รวม default+tracking)
  • NSTimer และ CADisplayLink จะไม่ทำงานหากไม่มี RunLoop ที่ทำงานอยู่บนเธรด
  • RunLoop observers ช่วยให้ตอบสนองต่อการเข้า/ออกจากโหมดและจุดเริ่มต้น/สิ้นสุดการประมวลผล

RunLoop คืออะไร

RunLoop — คือออบเจ็กต์โครงสร้างพื้นฐานของ Core Foundation ที่จัดระเบียบการประมวลผลเหตุการณ์บนเธรด โดยสาระสำคัญแล้วมันคือลูปอนันต์ (while true) ที่รอการมาถึงของเหตุการณ์ (sources) และส่งต่อไปยังตัวจัดการ เมื่อไม่มีเหตุการณ์ RunLoop จะทำให้เธรดเข้าสู่โหมดพัก (sleep) เพื่อประหยัดพลังงานแบตเตอรี่ เมื่อมีเหตุการณ์มา เธรดจะถูกปลุก ประมวลผลเหตุการณ์ และกลับเข้าสู่โหมดพักอีกครั้ง RunLoop มีอยู่ใน iOS/macOS เท่านั้น (XNU + Core Foundation) — ใน Android บทบาทนี้ดำเนินการโดย Looper

แต่ละเธรดมี RunLoop ไม่เกินหนึ่งตัว ซึ่งถูกสร้างแบบ lazy เมื่อมีการเรียกครั้งแรก สำหรับ Main Thread RunLoop จะถูกสร้างโดยอัตโนมัติเมื่อเปิดแอปพลิเคชัน สำหรับเธรดพื้นหลัง RunLoop จะไม่ถูกสร้างจนกว่าจะเรียก CFRunLoopGetCurrent() หรือ RunLoop.current RunLoop หลักของแอปพลิเคชันมีหน้าที่ประมวลผลเหตุการณ์สัมผัส การเรนเดอร์หน้าจอ การเรียกใช้บล็อก DispatchQueue.main และการดูแลเลเยอร์ Core Animation

RunLoop ไม่ใช่เธรด — เป็นกลไกภายในเธรด เธรดสามารถดำรงอยู่ได้โดยไม่มี RunLoop (ถ้าทำงานแบบซิงโครนัสและสิ้นสุด) แต่ RunLoop ไม่สามารถดำรงอยู่ได้โดยไม่มีเธรด เมื่อเธรดที่มี RunLoop ทำงานอยู่ไม่มีเหตุการณ์ มันจะไม่บล็อก CPU แต่อยู่ในสถานะรอ (waiting) — นี่คือความแตกต่างหลักจากลูป busy-wait ที่ใช้ CPU 100%

RunLoop ทำงานอย่างไร: กายวิภาคของวงจรเหตุการณ์

RunLoop ประมวลผลแหล่งเหตุการณ์สองประเภท: Input Sources (แหล่งข้อมูลนำเข้า) และ Timer Sources (ตัวจับเวลา) Input Sources ส่งเหตุการณ์แบบอะซิงโครนัส: การสัมผัส การเคลื่อนไหวของเมาส์ ข้อมูลจากซ็อกเก็ต ข้อความจากเธรดอื่น (performSelector:onThread:) Timer Sources ส่งเหตุการณ์แบบซิงโครนัสตามกำหนดเวลา: NSTimer, CADisplayLink นอกจากนี้ยังมี Observer — จุดเข้าสำหรับตรวจสอบสถานะของ RunLoop

วงจรของ RunLoop ประกอบด้วยเฟสตามลำดับ: เข้าสู่โหมด (kCFRunLoopEntry), การประมวลผล timer (kCFRunLoopBeforeTimers), การประมวลผลแหล่งข้อมูลนำเข้า (kCFRunLoopBeforeSources), การประมวลผลแหล่งที่มา (kCFRunLoopAfterWaiting), การรอ (sleep), การออกจากโหมด (kCFRunLoopExit) หากไม่มีเหตุการณ์ใดถูกประมวลผลในการวนซ้ำปัจจุบัน RunLoop จะทำให้เธรดเข้าสู่โหมดพักเป็นระยะเวลาไม่จำกัดจนกว่าจะถูกปลุกด้วยเหตุการณ์ใหม่

swift
import Foundation

// การสาธิตเฟสของ RunLoop ผ่าน Observer
func observeRunLoopActivities() {
    let observer = CFRunLoopObserverCreateWithHandler(
        nil,
        CFOptionFlags([[.entry, .beforeTimers, .beforeSources,
                           .afterWaiting, .exit]]),
        true,           // repeats
        0               // priority
    ) { observer, activity in
        switch activity {
        case .entry:
            print("Entry — RunLoop ถูกเปิดใช้งาน")
        case .beforeTimers:
            print("BeforeTimers — การประมวลผล timer")
        case .beforeSources:
            print("BeforeSources — การประมวลผลแหล่งที่มา")
        case .afterWaiting:
            print("AfterWaiting — การปลุกหลังจากพัก")
        case .exit:
            print("Exit — RunLoop สิ้นสุด")
        default:
            break
        }
    }

    CFRunLoopAddObserver(
        CFRunLoopGetCurrent(),
        observer,
        .commonModes
    )
}

// ตัวอย่าง: RunLoop ประมวลผล timer บนเธรดหลัก
func timerOnMainRunLoop() {
    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
        print("Tick: \(Date())")
    }

    // RunLoop.current.run() บน Main Thread ถูกเรียกโดย UIApplicationMain
    // โดยอัตโนมัติ — ไม่จำเป็นต้องเริ่มด้วยตนเอง
    RunLoop.current.run() // การเรียกนี้จะไม่กลับมาที่ Main Thread
}

ตัวอย่าง observeRunLoopActivities ลงทะเบียน Observer บน RunLoop หลักที่บันทึกแต่ละเฟสของวงจร ซึ่งมีประโยชน์สำหรับการดีบัก: หากคุณเห็นช่วงเวลาที่ยาวนานระหว่าง .beforeTimers และ .afterWaiting หมายความว่า RunLoop ถูกบล็อกโดยการดำเนินการบน Main Thread timerOnMainRunLoop แสดงให้เห็นว่า NSTimer ทำงานโดยอัตโนมัติบน RunLoop หลักอย่างไร — เมื่อสร้าง Timer.scheduledTimer timer จะถูกเพิ่มใน RunLoop ปัจจุบันในโหมดเริ่มต้น (.default mode)

RunLoop vs Looper (Android)

Android Looper — คือสิ่งที่คล้ายกับ RunLoop Looper.prepare() สร้างคิวข้อความ (MessageQueue) บนเธรด Looper.loop() เริ่มลูปประมวลผลอนันต์ Handler ส่งข้อความและ Runnable ไปยังคิวนี้ ความแตกต่างหลัก: RunLoop รองรับโหมด (modes) ในขณะที่ Android Looper ไม่รองรับ Looper ประมวลผลข้อความทั้งหมดโดยไม่มีการกรองตามโหมด ซึ่งทำให้เรียบง่ายกว่าแต่ยืดหยุ่นน้อยกว่าสำหรับสถานการณ์ที่มีลำดับความสำคัญ (เช่น การสกรอลล์ใน iOS จะถูกประมวลผลในโหมด .tracking แยกจากเหตุการณ์อื่น)

โหมด RunLoop: default, tracking, common

RunLoop Mode — คือชุดของแหล่งที่มา timer และผู้สังเกตการณ์ที่ทำงานอยู่ในขณะนั้น โหมดช่วยให้แยกการประมวลผลเหตุการณ์ตามลำดับความสำคัญ เมื่อผู้ใช้สกรอลล์ UITableView RunLoop จะสลับไปยังโหมด .tracking ซึ่งจะประมวลผลเฉพาะเหตุการณ์สกรอลล์และ timer/แอนิเมชันที่เกี่ยวข้องเท่านั้น แหล่งที่มาอื่นๆ ทั้งหมด (เช่น NSURLConnection) จะถูกหยุดชั่วคราวจนกว่าจะออกจากโหมดสกรอลล์

สามโหมดหลัก: .default (NSDefaultRunLoopMode) — โหมดหลักที่ประมวลผลเหตุการณ์ทั้งหมดยกเว้นการสกรอลล์; .tracking (UITrackingRunLoopMode) — เปิดใช้งานเมื่อสกรอลล์หรือนำทางด้วยท่าทาง; .common (NSRunLoopCommonModes) — ไม่ใช่โหมดแยก แต่เป็นชุดนามแฝงที่รวม .default + .tracking การเพิ่มแหล่งที่มาใน .commonModes จะเพิ่มโดยอัตโนมัติในทุกโหมดของชุด

โหมดค่าคงที่ Core Foundationค่าคงที่ Foundationเมื่อทำงาน
.defaultkCFRunLoopDefaultModeRunLoop.Mode.defaultสถานะปกติ ไม่มีการสกรอลล์
.trackingUITrackingRunLoopModeRunLoop.Mode.trackingการสกรอลล์, gesture recognizers
.commonkCFRunLoopCommonModesRunLoop.Mode.commonโหมดเสมือน: default + tracking
.initialRunkCFRunLoopInitialRunRunLoopModeการเรียกใช้ RunLoop ครั้งแรก

ทำไม NSTimer ถึงไม่ทำงานระหว่างการสกรอลล์

ปัญหาคลาสสิก: NSTimer ที่เพิ่มในโหมด .default จะหยุดทำงานระหว่างการสกรอลล์เพราะ RunLoop สลับไปยังโหมด .tracking และไม่ประมวลผล timer จาก .default วิธีแก้ไข — เพิ่ม timer ใน .commonModes: RunLoop.current.add(timer, forMode: .common) ซึ่งจะทำให้ Timer ทำงานทั้งใน .default และ .tracking ทางเลือกอื่น — ใช้ DispatchQueue.main.async แทน NSTimer เนื่องจาก GCD ทำงานในระดับเธรด ไม่ใช่โหมด RunLoop

RunLoop ในเธรดพื้นหลัง

เธรดพื้นหลัง ไม่มี RunLoop โดยค่าเริ่มต้น หากในเธรดพื้นหลังจำเป็นต้องเรียกใช้ NSTimer ประมวลผล NSInputStream/NSOutputStream หรือตอบสนองต่อ performSelector: ต้องสร้างและเริ่ม RunLoop ด้วยตนเอง หากไม่มี RunLoop timer และ performSelector: จะไม่มีวันทำงาน — เธรดจะเรียกใช้โค้ดและสิ้นสุดลงโดยไม่รอเหตุการณ์

ในการสร้าง RunLoop ในเธรดพื้นหลัง เพียงเรียก RunLoop.current.run() ที่ส่วนท้ายของการทำงานของเธรด การเรียกนี้จะบล็อกเธรดอย่างไม่มีกำหนดเพื่อประมวลผลเหตุการณ์ ในการหยุด ให้ใช้ CFRunLoopStop(CFRunLoopGetCurrent()) สิ่งสำคัญ: RunLoop.current สร้าง RunLoop แบบ lazy ในการเรียกครั้งแรก — ถ้าไม่เรียก run() มันจะไม่ประมวลผลเหตุการณ์ รูปแบบ: การกำหนดค่าแหล่งที่มา -> เพิ่มใน RunLoop -> เรียก run()

swift
import Foundation

// เธรดพื้นหลังที่มี RunLoop ของตัวเอง
class BackgroundRunLoopManager {

    private let thread: Thread
    private var isRunning = false

    init() {
        thread = Thread { [weak self] in
            // RunLoop ถูกสร้างโดยอัตโนมัติเมื่อเรียก RunLoop.current
            let runLoop = RunLoop.current

            // เราเพิ่ม Port ว่างเพื่อให้ RunLoop ทำงานอยู่
            runLoop.add(Port(), forMode: .default)

            // เริ่มการประมวลผลเหตุการณ์
            var isFinished = false
            while !isFinished {
                // run(mode:before:) คืนค่า true ถ้าประมวลผลเหตุการณ์แล้ว
                isFinished = !runLoop.run(mode: .default, before: Date.distantFuture)
            }
        }
        thread.name = "com.app.background-runloop"
    }

    func start() {
        thread.start()
        isRunning = true
    }

    func stop() {
        // การหยุด RunLoop บนเธรดพื้นหลัง
        self.perform(
            #selector(BackgroundRunLoopManager.stopRunLoop),
            on: thread,
            with: nil,
            waitUntilDone: false
        )
    }

    @objc
    private func stopRunLoop() {
        CFRunLoopStop(CFRunLoopGetCurrent())
        isRunning = false
    }
}

// การใช้งาน: timer บน RunLoop พื้นหลัง
let manager = BackgroundRunLoopManager()
manager.start()

// การส่งงานไปยัง RunLoop พื้นหลังผ่าน performSelector
manager.perform(
    #selector(BackgroundRunLoopManager.backgroundTask),
    on: manager.thread,
    with: nil,
    waitUntilDone: false
)

BackgroundRunLoopManager สร้างเธรดพื้นหลังที่มี RunLoop ถาวร การเพิ่ม Port() ว่างเป็นสิ่งจำเป็นเพื่อให้ RunLoop ไม่สิ้นสุดทันที — หากไม่มีแหล่งที่มา RunLoop.run() จะคืนค่า false และออก performSelector:onThread: ส่งข้อความไปยัง RunLoop พื้นหลัง — มันจะถูกประมวลผลเมื่อ RunLoop เข้าสู่เฟส BeforeSources Stop เรียก CFRunLoopStop บนเธรดพื้นหลังเพื่อสิ้นสุดวงจร

NSTimer สร้างเหตุการณ์ timer ที่ RunLoop ประมวลผลในเฟส BeforeTimers timer มีทั้งแบบ repeating (ทำซ้ำ) และ non-repeating (ครั้งเดียว) NSTimer ไม่รับประกันความแม่นยำ: ถ้า RunLoop ถูกบล็อกโดยการดำเนินการที่ยาวนาน timer จะทำงานหลังจากปลดบล็อก และการทำงานที่พลาดทั้งหมดจะรวมเป็นการทำงานเดียว (สำหรับ repeating timer — ไม่เกินหนึ่งการทำงานที่ “ตามทัน”)

CADisplayLink — timer เฉพาะทางที่ซิงค์กับอัตราการรีเฟรชหน้าจอ (60/120/144 Hz) ใช้สำหรับแอนิเมชันและการอัปเดตวิดีโอ CADisplayLink ถูกเพิ่มใน RunLoop และทำงานก่อนทุกเฟรมการเรนเดอร์ (ก่อนที่ Core Animation จะส่งเลเยอร์ไปเรนเดอร์) หากเฟรมถูกข้าม (display link ไม่สามารถทำงานใน 16 ms) การเรียกครั้งถัดไปจะเกิดขึ้นในรอบ VSync ถัดไป

swift
import UIKit

class AnimationController {

    private var displayLink: CADisplayLink?
    private var displayLinkTimer: Timer?
    private var startTime: CFTimeInterval = 0

    // CADisplayLink — แอนิเมชันกับ vsync
    func startDisplayLinkAnimation() {
        displayLink = CADisplayLink(target: self,
                                       selector: #selector(step))
        // การเพิ่มในโหมด .common — ทำงานแม้ระหว่างสกรอลล์
        displayLink?.add(to: .current, forMode: .common)
        startTime = CACurrentMediaTime()
    }

    @objc
    private func step(displayLink: CADisplayLink) {
        let elapsed = CACurrentMediaTime() - startTime
        // เรียกทุกเฟรม (60 FPS → ทุก 16.6 มิลลิวินาที)
        print("Frame at \(elapsed) seconds")

        if elapsed > 5.0 {
            displayLink.invalidate() // หยุดหลังจาก 5 วินาที
        }
    }

    // NSTimer — งานเป็นระยะ
    func startTimerInCommonMode() {
        displayLinkTimer?.invalidate()
        displayLinkTimer = Timer.scheduledTimer(
            withTimeInterval: 1.0,
            repeats: true
        ) { [weak self] timer in
            print("Timer tick")
        }

        // สำคัญ: เราเพิ่มใน .common มิฉะนั้น timer จะหยุดทำงานเมื่อสกรอลล์
        RunLoop.current.add(displayLinkTimer!, forMode: .common)
    }

    func stop() {
        displayLink?.invalidate()
        displayLinkTimer?.invalidate()
    }
}

ใน AnimationController CADisplayLink ถูกเพิ่มในโหมด .common ซึ่งรับประกันว่า step จะถูกเรียกในทุกเฟรมโดยไม่ขึ้นกับการสกรอลล์ displayLink.add(to: .current, forMode: .common) — รูปแบบมาตรฐานสำหรับแอนิเมชันที่ไม่ควรถูกขัดจังหวะระหว่างการสกรอลล์ NSTimer ถูกเพิ่มในโหมด .common เพื่อให้ทำงานระหว่างการสกรอลล์ หากไม่มีสิ่งนี้ timer จะทำงานเฉพาะในโหมด .default

RunLoop Observers: การตรวจสอบเหตุการณ์ของวงจร

CFRunLoopObserver — กลไกสำหรับตรวจสอบเฟสของ RunLoop ด้วย Observer คุณสามารถรับการแจ้งเตือนเกี่ยวกับการเข้าสู่โหมด การเริ่มต้นประมวลผล timer การเริ่มต้นประมวลผลแหล่งที่มา การปลุกหลังพัก การออกจากโหมด Observer ถูกใช้โดยเฟรมเวิร์กสำหรับความต้องการของตนเอง: Core Animation ใช้สำหรับเรนเดอร์เลเยอร์ก่อน RunLoop พัก UIKit ใช้สำหรับอัปเดตเลย์เอาต์หลังประมวลผลเหตุการณ์

นักพัฒนายังสามารถเพิ่ม Observer สำหรับวัตถุประสงค์ของตนเองได้ เช่น การวัดเวลาประมวลผลเหตุการณ์ (profiling) การดำเนินการที่ถูกเลื่อนออกไปก่อน RunLoop พัก (เมื่อ UI ได้รับการอัปเดตแล้วและผู้ใช้ไม่ได้โต้ตอบ) การบันทึกข้อมูลอัตโนมัติเมื่อไม่มีการใช้งานเป็นเวลานาน Observer ลงทะเบียนผ่าน CFRunLoopAddObserver โดยระบุโหมดและ bit mask ของกิจกรรมที่ตรวจสอบ

จุดที่มีประโยชน์ที่สุดสำหรับ Observer: .afterWaiting — ทำงานหลังจาก RunLoop ถูกปลุกและสามารถมีโค้ดที่ควรทำงานหลังจากการประมวลผลเหตุการณ์; .beforeTimers — ก่อนการประมวลผล timer ช่วยให้วัดเวลาที่ผ่านไปตั้งแต่การประมวลผลครั้งก่อน; .exit — ทำงานเมื่อ RunLoop หยุด มีประโยชน์สำหรับการล้างทรัพยากรของเธรดพื้นหลัง

CFRunLoopStop และการสิ้นสุดวงจร

CFRunLoopStop — ฟังก์ชันที่บังคับให้สิ้นสุดการวนซ้ำปัจจุบันของ RunLoop เมื่อเรียก CFRunLoopStop(CFRunLoopGetCurrent()) RunLoop จะสิ้นสุดการประมวลผลเหตุการณ์ปัจจุบันและออกจาก run() โดยคืนค่า false นี่คือวิธีมาตรฐานในการหยุด RunLoop บนเธรดพื้นหลัง บน Main Thread ไม่แนะนำให้ใช้ CFRunLoopStop — RunLoop หลักควรทำงานตลอดอายุของแอปพลิเคชัน สำหรับเธรดพื้นหลัง หลังจาก CFRunLoopStop เธรดอาจสิ้นสุดหรือดำเนินการโค้ดถัดไปหลังจาก run() ต่อไป

คำถามที่พบบ่อย

RunLoop ใน iOS คืออะไร?

RunLoop — วงจรประมวลผลเหตุการณ์ใน iOS ที่ implement โดย CFRunLoop (Core Foundation) และ NSRunLoop (Foundation) มันรอเหตุการณ์ (การสัมผัส timer แหล่งข้อมูลนำเข้า) และส่งต่อไปยังตัวจัดการบนเธรด แต่ละเธรดสามารถมี RunLoop ได้หนึ่งตัว แต่โดยอัตโนมัติจะถูกสร้างสำหรับ Main Thread เท่านั้น RunLoop จัดการโหมด (.default, .tracking, .common) โดยแยกการประมวลผลตามลำดับความสำคัญ

ทำไม NSTimer ถึงไม่ทำงานระหว่างการสกรอลล์?

NSTimer โดยค่าเริ่มต้นถูกเพิ่มในโหมด .default ของ RunLoop เมื่อผู้ใช้สกรอลล์ RunLoop จะสลับไปยังโหมด .tracking และไม่ประมวลผล timer จาก .default วิธีแก้ไข: เพิ่ม timer ในโหมด .common ผ่าน RunLoop.current.add(timer, forMode: .common) .common รวม .default และ .tracking ดังนั้น timer จึงทำงานในทั้งสองโหมด

จำเป็นต้องเริ่ม RunLoop ในเธรดพื้นหลังหรือไม่?

เฉพาะเมื่อ เธรดพื้นหลังใช้ timer (NSTimer), performSelector:onThread:, NSInputStream/NSOutputStream หรือเหตุการณ์ Source ถ้าเธรดทำงานแบบซิงโครนัส (ดาวน์โหลดไฟล์ คำนวณ) และสิ้นสุด — ไม่จำเป็นต้องใช้ RunLoop ในการเริ่ม ให้เรียก RunLoop.current.run() หลังจากกำหนดค่าแหล่งที่มา ในการหยุด — CFRunLoopStop(CFRunLoopGetCurrent())

RunLoop แตกต่างจาก GCD DispatchQueue อย่างไร?

RunLoop ทำงานในระดับเธรดและประมวลผลเหตุการณ์ตามลำดับพร้อมรองรับโหมด DispatchQueue — นามธรรมของพูลเธรด งานจะถูกดำเนินการบนเธรดว่างใดๆ GCD ไม่รองรับโหมดและทำงานแยกจาก RunLoop DispatchQueue.main ใช้ RunLoop หลักในการเรียกใช้บล็อก — นี่คือจุดตัดเพียงจุดเดียว สำหรับงานพื้นหลัง GCD เหมาะสมกว่า

CADisplayLink เกี่ยวข้องกับ RunLoop อย่างไร?

CADisplayLink — timer ที่ซิงค์กับ VSync (อัตราการรีเฟรชหน้าจอ) มันถูกเพิ่มใน RunLoop และทำงานก่อนทุกเฟรมการเรนเดอร์ในเฟส BeforeTimers CADisplayLink ทำงานบน Main Thread เท่านั้นเนื่องจากการเรนเดอร์หน้าจอเกิดขึ้นที่นั่น สำหรับแอนิเมชันต่อเนื่องระหว่างการสกรอลล์ ให้เพิ่มในโหมด .common: displayLink.add(to: .current, forMode: .common)

สรุป

  • RunLoop — event loop ของ iOS: ประมวลผลการสัมผัส timer แหล่งข้อมูลนำเข้าบนเธรด; Main Thread มี RunLoop อัตโนมัติ
  • สามโหมด: .default (ทั่วไป), .tracking (สกรอลล์), .common (default + tracking) — จัดการการกรองเหตุการณ์
  • NSTimer ใน .default ไม่ทำงานระหว่างสกรอลล์ — วิธีแก้ไข: เพิ่มในโหมด .common ผ่าน RunLoop.current.add(timer, forMode: .common)
  • เธรดพื้นหลัง ไม่มี RunLoop โดยค่าเริ่มต้น — สำหรับ timer และ performSelector: ต้องเริ่มด้วยตนเองผ่าน RunLoop.current.run()
  • CADisplayLink — timer ทุกเฟรม VSync จำเป็นสำหรับแอนิเมชันที่ราบรื่น; เพิ่มใน .common เพื่อทำงานระหว่างสกรอลล์
  • RunLoop Observer — การตรวจสอบเฟส: Entry, BeforeTimers, BeforeSources, AfterWaiting, Exit; ใช้สำหรับ profiling
  • RunLoop ≠ Looper: RunLoop ของ iOS รองรับโหมดและ timer, Looper ของ Android เรียบง่ายกว่า — ไม่มีโหมด, Handler + MessageQueue

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม