iOS-da RunLoop: nima, ish rejimlari va hodisalar sikli

Muallif: IT Sectr Nashr etilgan: 2026-03-16 O'qish vaqti: 11 daq

RunLoop — iOS-da CFRunLoop (Core Foundation) va NSRunLoop (Foundation) obyektlari tomonidan amalga oshirilgan hodisalarni qayta ishlash sikli. Bu hodisalarni (teginishlar, taymerlar, kirish manbalari, bildirishnomalar) kutadigan va ularni oqimdagi tegishli ishlovchilarga yo'naltiradigan mexanizmdir. iOS-dagi har bir oqim maksimal bitta RunLoop-ga ega, ammo u avtomatik ravishda faqat Main Thread uchun yaratiladi. Apple CFRunLoop hujjatlariga ko'ra, RunLoop fon oqimlarida taymerlar, animatsiyalar va manba monitoringi ishlashi uchun juda muhimdir.

Asosiy ma'lumotlar

  • RunLoop — oqimda hodisalarni qayta ishlaydigan event loop: teginishlar, taymerlar, kirish manbalari
  • Main Thread avtomatik RunLoop-ga ega (CFRunLoopGetMain()), fon oqimlarini qo'lda ishga tushirish kerak
  • Uch rejim: .default (asosiy), .tracking (siljish), .common (default+tracking birlashtiradi)
  • NSTimer va CADisplayLink oqimda faol RunLoop bo'lmasa ishlamaydi
  • RunLoop observers rejimlarga kirish/chiqish va qayta ishlash boshlanishi/tugashiga reaksiya berishga imkon beradi

RunLoop nima

RunLoop — oqimda hodisalarni qayta ishlashni tashkil qiladigan Core Foundation infratuzilma obyekti. Mohiyatan bu, hodisalarning (sources) kelishini kutadigan va ularni ishlovchilarga uzatadigan cheksiz sikldir (while true). Hodisalar bo'lmaganda, RunLoop batareya energiyasini tejab, oqimni uyqu rejimiga (sleep) o'tkazadi. Hodisa kelganda, oqim uyg'onadi, uni qayta ishlaydi va yana uxlaydi. RunLoop faqat iOS/macOS (XNU + Core Foundation) da mavjud — Android-da uning rolini Looper bajaradi.

Har bir oqim eng ko'pi bilan bitta RunLoop-ga ega va u birinchi murojaatda dangasa (lazy) yaratiladi. Main Thread uchun RunLoop ilova ishga tushirilganda avtomatik yaratiladi. Fon oqimlari uchun RunLoop CFRunLoopGetCurrent() yoki RunLoop.current chaqirilmaguncha yaratilmaydi. Ilovaning asosiy RunLoop-i teginish hodisalarini qayta ishlash, ekran renderi, DispatchQueue.main bloklarini bajarish va Core Animation qatlamlariga xizmat ko'rsatish uchun javobgardir.

RunLoop oqim emas — bu oqim ichidagi mexanizm. Oqim RunLoop-siz mavjud bo'lishi mumkin (sinxron vazifani bajarib tugatsa), lekin RunLoop oqimsiz mavjud bo'la olmaydi. Faol RunLoop-ga ega oqimda hodisalar bo'lmaganda, CPU bloklanmaydi, aksincha kutish holatida (waiting) bo'ladi — bu 100% CPU sarflaydigan busy-wait siklidan asosiy farqdir.

RunLoop qanday ishlaydi: hodisalar siklining anatomiyasi

RunLoop ikki turdagi hodisa manbasini qayta ishlaydi: Input Sources (kirish manbalari) va Timer Sources (taymerlar). Input Sources asinxron hodisalarni yetkazadi: teginishlar, sichqoncha harakatlari, soketdan ma'lumotlar, boshqa oqimlardan xabarlar (performSelector:onThread:). Timer Sources jadval bo'yicha sinxron hodisalarni yetkazadi: NSTimer, CADisplayLink. Shuningdek, RunLoop holatini kuzatish uchun Observers — kirish nuqtalari ham mavjud.

RunLoop sikli ketma-ket fazalardan iborat: rejimga kirish (kCFRunLoopEntry), taymerlarni qayta ishlash (kCFRunLoopBeforeTimers), kirish manbalarini qayta ishlash (kCFRunLoopBeforeSources), manbalarni qayta ishlash (kCFRunLoopAfterWaiting), kutish (sleep), rejimdan chiqish (kCFRunLoopExit). Agar joriy iteratsiyada hech qanday hodisa qayta ishlanmagan bo'lsa, RunLoop yangi hodisa bilan uyg'onguncha oqimni noma'lum muddatga uyquga yuboradi.

swift
import Foundation

// RunLoop fazalarini Observer orqali namoyish qilish
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 faollashtirildi")
        case .beforeTimers:
            print("BeforeTimers — taymerlarni qayta ishlash")
        case .beforeSources:
            print("BeforeSources — manbalarni qayta ishlash")
        case .afterWaiting:
            print("AfterWaiting — uyqudan uyg'onish")
        case .exit:
            print("Exit — RunLoop tugallandi")
        default:
            break
        }
    }

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

// Misol: RunLoop asosiy oqimda taymerni qayta ishlaydi
func timerOnMainRunLoop() {
    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
        print("Tick: \(Date())")
    }

    // RunLoop.current.run() Main Thread-da UIApplicationMain tomonidan chaqiriladi
    // avtomatik — qo'lda ishga tushirish shart emas
    RunLoop.current.run() // Bu chaqiruv Main Thread-ga qaytmaydi
}

observeRunLoopActivities namunasi asosiy RunLoop-da siklning har bir bosqichini qayd qiluvchi Observer-ni ro'yxatdan o'tkazadi. Bu disk raskadrovka uchun foydalidir: .beforeTimers va .afterWaiting o'rtasida uzoq intervalni ko'rsangiz, RunLoop Main Thread-da operatsiya tomonidan bloklanganligini anglatadi. timerOnMainRunLoop NSTimerning asosiy RunLoop-da avtomatik ravishda qanday ishlashini ko'rsatadi — Timer.scheduledTimer yaratilganda taymer joriy RunLoop-ga sukut bo'yicha (.default mode) qo'shiladi.

RunLoop vs Looper (Android)

Android Looper — RunLoop-ning analogidir. Looper.prepare() oqimda xabarlar navbatini (MessageQueue) yaratadi, Looper.loop() cheksiz qayta ishlash siklini ishga tushiradi. Handler xabarlar va Runnable-larni ushbu navbatga yuboradi. Asosiy farq: RunLoop rejimlarni (modes) qo'llab-quvvatlaydi, Android Looper esa yo'q. Looper barcha xabarlarni rejim bo'yicha filtrlashsiz qayta ishlaydi, bu uni soddalashtiradi, lekin prioritetli stsenariylarda kamroq moslashuvchan qiladi (masalan, iOS-da siljish .tracking rejimida boshqa hodisalardan alohida qayta ishlanadi).

RunLoop Rejimlari: default, tracking, common

RunLoop Mode — hozirda faol bo'lgan manbalar, taymerlar va kuzatuvchilar to'plamidir. Rejimlar hodisalarni qayta ishlashni prioritetlar bo'yicha izolyatsiya qilishga imkon beradi. Foydalanuvchi UITableView-ni siljitganda, RunLoop .tracking rejimiga o'tadi, bunda faqat siljish hodisalari va tegishli taymerlar/animatsiyalar qayta ishlanadi. Boshqa barcha manbalar (masalan, NSURLConnection) siljish rejimidan chiqishgacha to'xtatiladi.

Uch asosiy rejim: .default (NSDefaultRunLoopMode) — siljishdan tashqari barcha hodisalar qayta ishlanadigan asosiy rejim; .tracking (UITrackingRunLoopMode) — siljish yoki imo-ishora navigatsiyasi paytida faollashadi; .common (NSRunLoopCommonModes) — alohida rejim emas, balki .default + .tracking-ni o'z ichiga olgan taxalluslar to'plamidir. Manbani .commonModes-ga qo'shish uni avtomatik ravishda to'plamning barcha rejimlariga qo'shadi.

RejimCore Foundation doimiysiFoundation doimiysiQachon faol
.defaultkCFRunLoopDefaultModeRunLoop.Mode.defaultOddiy holat, siljishsiz
.trackingUITrackingRunLoopModeRunLoop.Mode.trackingSiljish, gesture recognizers
.commonkCFRunLoopCommonModesRunLoop.Mode.commonTaxallus-rejim: default + tracking
.initialRunkCFRunLoopInitialRunRunLoopModeRunLoop-ning birinchi ishga tushirilishi

Nega NSTimer siljish paytida ishlamaydi

Klassik muammo: NSTimer .default rejimiga qo'shilganda siljish paytida ishlashni to'xtatadi, chunki RunLoop .tracking rejimiga o'tadi va .default-dan taymerlarni qayta ishlamaydi. Yechim — taymerni .commonModes-ga qo'shing: RunLoop.current.add(timer, forMode: .common). Bu taymerning ham .default, ham .tracking rejimida ishlashiga sabab bo'ladi. Alternativ NSTimer o'rniga DispatchQueue.main.async ishlatishdir, chunki GCD oqim darajasida ishlaydi, RunLoop rejimlarida emas.

Fon oqimlarida RunLoop

Fon oqimlari sukut bo'yicha RunLoop-ga ega emas. Fon oqimida NSTimer ishga tushirish, NSInputStream/NSOutputStream qayta ishlash yoki performSelector: ga reaksiya berish kerak bo'lsa, RunLoop-ni qo'lda yaratish va ishga tushirish lozim. RunLoop bo'lmasa, taymer va performSelector: hech qachon ishlamaydi — oqim kodni ishga tushirib, hodisalarni kutmasdan tugaydi.

Fon oqimida RunLoop yaratish uchun oqim oxirida RunLoop.current.run() chaqirish kifoya. Bu chaqiruv oqimni noma'lum muddatga bloklaydi va hodisalarni qayta ishlaydi. To'xtatish uchun CFRunLoopStop(CFRunLoopGetCurrent()) dan foydalaning. Muhim: RunLoop.current birinchi murojaatda dangasa RunLoop yaratadi — agar run() chaqirmasangiz, hodisalarni qayta ishlamaydi. Namuna: manbalarni konfiguratsiya qilish -> RunLoop-ga qo'shish -> run() chaqirish.

swift
import Foundation

// O'z RunLoop-iga ega fon oqimi
class BackgroundRunLoopManager {

    private let thread: Thread
    private var isRunning = false

    init() {
        thread = Thread { [weak self] in
            // RunLoop.current chaqirilganda RunLoop avtomatik yaratiladi
            let runLoop = RunLoop.current

            // RunLoop-ni faol ushlab turish uchun port qo'shamiz
            runLoop.add(Port(), forMode: .default)

            // Hodisalarni qayta ishlashni ishga tushiramiz
            var isFinished = false
            while !isFinished {
                // run(mode:before:) hodisa qayta ishlanganda true qaytaradi
                isFinished = !runLoop.run(mode: .default, before: Date.distantFuture)
            }
        }
        thread.name = "com.app.background-runloop"
    }

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

    func stop() {
        // Fon oqimida RunLoop-ni to'xtatish
        self.perform(
            #selector(BackgroundRunLoopManager.stopRunLoop),
            on: thread,
            with: nil,
            waitUntilDone: false
        )
    }

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

// Foydalanish: fon RunLoop-da taymer
let manager = BackgroundRunLoopManager()
manager.start()

// Vazifani performSelector orqali fon RunLoop-ga yuborish
manager.perform(
    #selector(BackgroundRunLoopManager.backgroundTask),
    on: manager.thread,
    with: nil,
    waitUntilDone: false
)

BackgroundRunLoopManager doimiy RunLoop bilan fon oqimini yaratadi. Bo'sh Port() qo'shish RunLoop-ning darhol tugamasligi uchun zarur — manbalarsiz RunLoop.run() false qaytaradi va chiqadi. performSelector:onThread: fon RunLoop-ga xabar yuboradi — RunLoop BeforeSources fazasiga kirganda qayta ishlanadi. Stop fon oqimida CFRunLoopStop chaqirib siklini to'xtatadi.

NSTimer RunLoop BeforeTimers fazasida qayta ishlaydigan taymer hodisasini yaratadi. Taymerlar repeating (takrorlanuvchi) va non-repeating (bir martalik) bo'lishi mumkin. NSTimer aniqlik kafolatini bermaydi: agar RunLoop uzoq operatsiya bilan bloklangan bo'lsa, taymer bloklash bartaraf etilgandan keyin ishlaydi va barcha o'tkazib yuborilgan ishlashlar bittaga birlashtiriladi (takrorlanuvchi taymer uchun — bittadan ortiq bo'lmagan „yetib olish“ ishlashi).

CADisplayLink — ekran yangilanish chastotasi bilan sinxronlashtirilgan maxsus taymer (60/120/144 Gts). Animatsiyalar va video yangilanishlar uchun ishlatiladi. CADisplayLink RunLoop-ga qo'shiladi va har bir render kadridan oldin (Core Animation qatlamni render-ga yuborishdan oldin) ishlaydi. Agar kadr o'tkazib yuborilgan bo'lsa (display link 16 ms ichida ishlay olmagan bo'lsa), keyingi chaqiruv keyingi VSync siklida sodir bo'ladi.

swift
import UIKit

class AnimationController {

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

    // CADisplayLink — vsync bilan animatsiya
    func startDisplayLinkAnimation() {
        displayLink = CADisplayLink(target: self,
                                       selector: #selector(step))
        // .common rejimiga qo'shish — siljish paytida ham ishlaydi
        displayLink?.add(to: .current, forMode: .common)
        startTime = CACurrentMediaTime()
    }

    @objc
    private func step(displayLink: CADisplayLink) {
        let elapsed = CACurrentMediaTime() - startTime
        // Har bir kadrda chaqiriladi (60 FPS → har 16.6 ms)
        print("Frame at \(elapsed) seconds")

        if elapsed > 5.0 {
            displayLink.invalidate() // 5 soniyadan keyin to'xtash
        }
    }

    // NSTimer — davriy vazifa
    func startTimerInCommonMode() {
        displayLinkTimer?.invalidate()
        displayLinkTimer = Timer.scheduledTimer(
            withTimeInterval: 1.0,
            repeats: true
        ) { [weak self] timer in
            print("Timer tick")
        }

        // KALIT: .common-ga qo'shamiz, aks holda taymer siljishda muzlaydi
        RunLoop.current.add(displayLinkTimer!, forMode: .common)
    }

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

AnimationController-da CADisplayLink .common rejimiga qo'shilgan, bu siljishdan qat'iy nazar har bir kadrda step chaqirilishini kafolatlaydi. displayLink.add(to: .current, forMode: .common) — siljish paytida uzilmasligi kerak bo'lgan animatsiyalar uchun standart naqshdir. NSTimer ham siljish paytida ishlashi uchun .common rejimiga qo'shilgan. Busiz taymer faqat .default rejimida ishlagan bo'lardi.

RunLoop Observers: sikl hodisalarini monitoring qilish

CFRunLoopObserver — RunLoop fazalarini kuzatish mexanizmi. Observer yordamida rejimga kirish, taymerlarni qayta ishlash boshlanishi, manbalarni qayta ishlash boshlanishi, uyqudan uyg'onish, rejimdan chiqish haqida bildirishnomalar olish mumkin. Observer-lar freymvorklar tomonidan o'z ehtiyojlari uchun ishlatiladi: Core Animation ularni RunLoop uyqusidan oldin qatlamlarni render qilish, UIKit — hodisalarni qayta ishlagandan keyin layout-ni yangilash uchun ishlatadi.

Dasturchi ham o'z maqsadlari uchun Observer-lar qo'shishi mumkin. Masalan: hodisalarni qayta ishlash vaqtini o'lchash (profillash), RunLoop uyqusidan oldin kechiktirilgan operatsiyalarni bajarish (UI allaqachon yangilangan va foydalanuvchi o'zaro aloqada emas), uzoq harakatsizlik paytida avtomatik ma'lumotlarni saqlash. Observer CFRunLoopAddObserver orqali rejim va kuzatiladigan faoliyatlarning bit maskasi bilan ro'yxatdan o'tkaziladi.

Observer uchun eng foydali nuqtalar: .afterWaiting — RunLoop uyg'onganidan keyin bajariladi va hodisa qayta ishlanganidan keyin ishlashi kerak bo'lgan kodni o'z ichiga olishi mumkin; .beforeTimers — taymerlarni qayta ishlashdan oldin, oldingi qayta ishlashdan o'tgan vaqtni o'lchashga imkon beradi; .exit — RunLoop to'xtatilganda ishlaydi, fon oqimi resurslarini tozalash uchun foydalidir.

CFRunLoopStop va siklning tugallanishi

CFRunLoopStop — joriy RunLoop iteratsiyasini majburiy tugatadigan funksiyadir. CFRunLoopStop(CFRunLoopGetCurrent()) chaqirilganda RunLoop joriy hodisani qayta ishlashni tugatadi va run() dan false qaytarib chiqadi. Bu fon oqimida RunLoop-ni to'xtatishning standart usulidir. Main Thread-da CFRunLoopStop tavsiya etilmaydi — asosiy RunLoop ilovaning butun umri davomida ishlashi kerak. Fon oqimlari uchun CFRunLoopStop-dan keyin oqim tugashi yoki run() dan keyingi kodni bajarishni davom ettirishi mumkin.

Tez-tez beriladigan savollar

iOS-da RunLoop nima?

RunLoop — CFRunLoop (Core Foundation) va NSRunLoop (Foundation) tomonidan amalga oshirilgan iOS-da hodisalar siklidir. Hodisalarni (teginishlar, taymerlar, kirish manbalari) kutadi va ularni oqimdagi ishlovchilarga yo'naltiradi. Har bir oqim bitta RunLoop-ga ega bo'lishi mumkin, ammo avtomatik ravishda faqat Main Thread uchun yaratiladi. RunLoop rejimlarni (.default, .tracking, .common) boshqaradi, qayta ishlashni prioritetlar bo'yicha izolyatsiya qiladi.

Nega NSTimer siljish paytida ishlamaydi?

NSTimer sukut bo'yicha RunLoop-ning .default rejimiga qo'shiladi. Foydalanuvchi siljitganda, RunLoop .tracking rejimiga o'tadi va .default-dan taymerlarni qayta ishlamaydi. Yechim: taymerni .common rejimiga qo'shing: RunLoop.current.add(timer, forMode: .common). .common .default va .tracking-ni birlashtiradi, shuning uchun taymer ikkala rejimda ishlaydi.

Fon oqimida RunLoop-ni ishga tushirish kerakmi?

Faqat fon oqimi taymerlardan (NSTimer), performSelector:onThread:, NSInputStream/NSOutputStream yoki Source hodisalaridan foydalansa. Agar oqim sinxron vazifani (fayl yuklash, hisoblash) bajarib tugatsa — RunLoop kerak emas. Ishga tushirish uchun manbalarni konfiguratsiya qilgandan keyin RunLoop.current.run() chaqiring. To'xtatish uchun — CFRunLoopStop(CFRunLoopGetCurrent()).

RunLoop GCD DispatchQueue-dan nima bilan farq qiladi?

RunLoop oqim darajasida ishlaydi va hodisalarni rejimlar (modes) qo'llab-quvvatlagan holda ketma-ket qayta ishlaydi. DispatchQueue — oqim hovuzining abstraksiyasidir, vazifalar istalgan bo'sh oqimda bajariladi. GCD rejimlarni qo'llab-quvvatlamaydi va RunLoop-dan mustaqil mavjud. DispatchQueue.main bloklarni bajarish uchun asosiy RunLoop-dan foydalanadi — bu yagona kesishish nuqtasidir. Fon vazifalari uchun GCD afzalroqdir.

CADisplayLink RunLoop bilan qanday bog'liq?

CADisplayLink — VSync (ekran yangilanish chastotasi) bilan sinxronlashtirilgan taymerdir. RunLoop-ga qo'shiladi va BeforeTimers fazasida har bir render kadridan oldin ishlaydi. CADisplayLink faqat Main Thread-da ishlaydi, chunki ekran renderi u erda sodir bo'ladi. Siljish paytida uzluksiz animatsiyalar uchun uni .common rejimiga qo'shing: displayLink.add(to: .current, forMode: .common).

Xulosalar

  • RunLoop — iOS hodisa sikli: oqimda teginishlar, taymerlar, kirish manbalarini qayta ishlaydi; Main Thread avtomatik RunLoop-ga ega
  • Uch rejim: .default (umumiy), .tracking (siljish), .common (default + tracking) — hodisa filtrlashni boshqaradi
  • .default-dagi NSTimer siljish paytida ishlamaydi — yechim: RunLoop.current.add(timer, forMode: .common) bilan .common rejimiga qo'shish
  • Fon oqimlari sukut bo'yicha RunLoop-ga ega emas — taymerlar va performSelector: uchun RunLoop.current.run() bilan qo'lda ishga tushirish talab qilinadi
  • CADisplayLink — har bir VSync kadri uchun taymer, silliq animatsiyalar uchun zarur; siljish paytida ishlash uchun .common-ga qo'shiladi
  • RunLoop Observer — fazalar monitoringi: Entry, BeforeTimers, BeforeSources, AfterWaiting, Exit; profillash uchun ishlatiladi
  • RunLoop ≠ Looper: iOS RunLoop rejimlar va taymerlarni qo'llab-quvvatlaydi, Android Looper soddaroq — rejimlar yo'q, Handler + MessageQueue

Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz

IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.

Loyihani muhokama qilish

Shuningdek o'qing