RunLoop — iOS में एक इवेंट प्रोसेसिंग साइकल, जो CFRunLoop (Core Foundation) और NSRunLoop (Foundation) ऑब्जेक्ट्स द्वारा कार्यान्वित होता है। यह तंत्र घटनाओं (स्पर्श, टाइमर, इनपुट स्रोत, नोटिफिकेशन) की प्रतीक्षा करता है और उन्हें थ्रेड पर उपयुक्त हैंडलर्स तक पहुंचाता है। iOS में प्रत्येक थ्रेड में अधिकतम एक RunLoop हो सकता है, लेकिन यह स्वचालित रूप से केवल Main Thread के लिए बनाया जाता है। Apple CFRunLoop Documentation के अनुसार, RunLoop बैकग्रॉंड थ्रेड्स पर टाइमर्स, एनिमेशन और स्रोतों की निगरानी के लिए महत्वपूर्ण है।
मुख्य बातें
RunLoop एक Core Foundation अधिसंरचना ऑब्जेक्ट है जो एक थ्रेड पर घटना प्रोसेसिंग को व्यवस्थित करता है। मूल रूप से, यह एक अनंत लूप (while true) है जो घटनाओं (sources) के आने की प्रतीक्षा करता है और उन्हें हैंडलर्स तक पहुंचाता है। जब कोई घटना नहीं होती, RunLoop थ्रेड को नींद में डाल देता है, बैटरी बचाता है। जब कोई घटना आती है, थ्रेड जागता है, उसे संसाधित करता है, और वापस सो जाता है। 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 को ब्लॉक नहीं करता बल्कि प्रतीक्षा स्थिति में चला जाता है — यह busy-wait लूप से मुख्य अंतर है जो 100% CPU की खपत करता है।
RunLoop दो प्रकार के घटना स्रोतों को संसाधित करता है: Input Sources (इनपुट स्रोत) और Timer Sources (टाइमर स्रोत)। Input Sources असिंक्रोनस घटनाओं को पहुंचाते हैं: स्पर्श, मौस की हरकतें, सॉकेट डेटा, अन्य थ्रेड्स से संदेश (performSelector:onThread:)। Timer Sources निर्धारित सिंक्रोनस घटनाओं को पहुंचाते हैं: NSTimer, CADisplayLink। Observers भी हैं — RunLoop स्थिति की निगरानी के लिए प्रवेश बिंदु।
RunLoop साइकल में अनुक्रमिक चरण होते हैं: मोड में प्रवेश (kCFRunLoopEntry), टाइमर प्रोसेसिंग (kCFRunLoopBeforeTimers), इनपुट स्रोत प्रोसेसिंग (kCFRunLoopBeforeSources), स्रोत हैंडलिंग (kCFRunLoopAfterWaiting), प्रतीक्षा (sleep), और मोड से बाहर निकलना (kCFRunLoopExit)। यदि वर्तमान पुनरावृत्ति में कोई घटना संसाधित नहीं होती, RunLoop थ्रेड को अनिश्चित रूप से नींद में डाल देता है जब तक कोई नई घटना उसे जाग्रूत नहीं करती।
import Foundation
// Observer के माध्यम से RunLoop चरणों का प्रदर्शन
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 — टाइमर प्रोसेसिंग")
case .beforeSources:
print("BeforeSources — स्रोत प्रोसेसिंग")
case .afterWaiting:
print("AfterWaiting — नींद के बाद जागना")
case .exit:
print("Exit — RunLoop समाप्त")
default:
break
}
}
CFRunLoopAddObserver(
CFRunLoopGetCurrent(),
observer,
.commonModes
)
}
// उदाहरण: RunLoop मुख्य थ्रेड पर एक टाइमर संसाधित करता है
func timerOnMainRunLoop() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
print("Tick: \(Date())")
}
// Main Thread पर RunLoop.current.run() UIApplicationMain द्वारा कॉल किया जाता है
// स्वचालित रूप से — मैनुअली शुरू करने की आवश्यकता नहीं
RunLoop.current.run() // यह कॉल Main Thread पर वापस नहीं आएगा
}
observeRunLoopActivities उदाहरण मुख्य RunLoop पर एक Observer पंजीकरित करता है जो साइकल के प्रत्येक चरण को लॉग करता है। यह डीबगिंग के लिए उपयोगी है: यदि आप .beforeTimers और .afterWaiting के बीच एक लंबा अंतराल देखते हैं, तो इसका मतलब है कि RunLoop Main Thread पर एक ऑपरेशन द्वारा अवरुद्ध है। timerOnMainRunLoop दिखाता है कि NSTimer मुख्य RunLoop पर स्वचालित रूप से कैसे काम करता है — Timer.scheduledTimer बनाते समय, यह टाइमर को डिफ़ॉल्ट रूप से (.default mode) वर्तमान RunLoop में जोड़ता है।
Android Looper RunLoop का एक एनालॉग है। Looper.prepare() एक थ्रेड पर एक संदेश क्यू (MessageQueue) बनाता है, Looper.loop() एक अनंत प्रोसेसिंग लूप शुरू करता है। Handler इस क्यू में संदेश और Runnable भेजता है। मुख्य अंतर: RunLoop मोड का समर्थन करता है, जबकि Android Looper नहीं करता। Looper बिना मोड फ़िल्टरिंग के सभी संदेशों को संसाधित करता है, जो इसे सरल बनाता है लेकिन प्राथमिकता परिदृश्यों के लिए कम लचीला बनाता है (उदाहरण के लिए, iOS में स्क्रॉलिंग को .tracking मोड में अन्य घटनाओं से अलग संभाला जाता है)।
RunLoop Mode स्रोतों, टाइमर्स और ऑब्जर्वर्स का एक समूह है जो एक दिए गए पल में सक्रिय होते हैं। मोड प्राथमिकता के अनुसार घटना प्रोसेसिंग को अलग-थलग करने की अनुमति देते हैं। जब कोई उपयोगकर्ता UITableView को स्क्रॉल करता है, RunLoop .tracking मोड में बदल जाता है, जिसमें केवल स्क्रॉल घटनाएँ और संबंधित टाइमर/एनिमेशन ही संसाधित होते हैं। अन्य सभी स्रोत (जैसे NSURLConnection) स्क्रॉल मोड से बाहर निकलने तक निलंबित रहते हैं।
तीन मुख्य मोड: .default (NSDefaultRunLoopMode) — प्राथमिक मोड जिसमें स्क्रॉलिंग को छोड़कर सभी घटनाएँ संसाधित होती हैं; .tracking (UITrackingRunLoopMode) — स्क्रॉलिंग या जेस्चर नेविगेशन के दौरान सक्रिय होता है; .common (NSRunLoopCommonModes) — कोई अलग मोड नहीं बल्कि एक उपनामों का समूह है जिसमें .default + .tracking शामिल है। एक स्रोत को .commonModes में जोड़ने से यह स्वचालित रूप से समूह के सभी मोड्स में जुड़ जाता है।
| मोड | Core Foundation स्थिरांक | Foundation स्थिरांक | कब सक्रिय |
|---|---|---|---|
| .default | kCFRunLoopDefaultMode | RunLoop.Mode.default | सामान्य स्थिति, कोई स्क्रॉल नहीं |
| .tracking | UITrackingRunLoopMode | RunLoop.Mode.tracking | स्क्रॉलिंग, जेस्चर रिकॉग्नाइज़र्स |
| .common | kCFRunLoopCommonModes | RunLoop.Mode.common | छद्म-मोड: default + tracking |
| .initialRun | kCFRunLoopInitialRunRunLoopMode | — | RunLoop का पहला लॉन्च |
एक क्लासिक समस्या: NSTimer जो .default मोड में जोड़ा गया है, स्क्रॉलिंग के दौरान काम करना बंद कर देता है क्योंकि RunLoop .tracking मोड में बदल जाता है और .default से टाइमर्स को संसाधित नहीं करता। समाधान — टाइमर को .commonModes में जोड़ें: RunLoop.current.add(timer, forMode: .common)। यह Timer को .default और .tracking दोनों में काम करने के लिए मजबूर करता है। एक वैकल्पिक उपाय NSTimer के बजाय DispatchQueue.main.async का उपयोग करना है, क्योंकि GCD थ्रेड स्तर पर काम करता है, RunLoop मोड स्तर पर नहीं।
बैकग्रॉंड थ्रेड्स में डिफ़ॉल्ट रूप से RunLoop नहीं होता। यदि आपको बैकग्रॉंड थ्रेड पर NSTimer चलाना, NSInputStream/NSOutputStream संसाधित करना या performSelector: का जवाब देना है, तो आपको मैनुअली RunLoop बनाना और शुरू करना होगा। RunLoop के बिना, टाइमर और performSelector: कभी नहीं चलेंगे — थ्रेड कोड निष्पादित करेगा और घटनाओं की प्रतीक्षा किए बिना बाहर निकल जाएगा।
बैकग्रॉंड थ्रेड पर RunLoop बनाने के लिए, थ्रेड के काम के अंत में RunLoop.current.run() कॉल करें। यह कॉल थ्रेड को अनिश्चित रूप से अवरुद्ध करता है, घटनाओं को संसाधित करता है। इसे रोकने के लिए, CFRunLoopStop(CFRunLoopGetCurrent()) का उपयोग करें। महत्वपूर्ण: RunLoop.current पहली पहुंच पर आलसी रूप से RunLoop बनाता है — यदि run() कॉल नहीं किया जाता, तो यह घटनाओं को संसाधित नहीं करेगा। पैटर्न: स्रोत कॉन्फ़िगर करें -> RunLoop में जोड़ें -> run() कॉल करें।
import Foundation
// अपने RunLoop के साथ बैकग्रॉंड थ्रेड
class BackgroundRunLoopManager {
private let thread: Thread
private var isRunning = false
init() {
thread = Thread { [weak self] in
// RunLoop.current कॉल करने पर RunLoop स्वचालित रूप से बनता है
let runLoop = RunLoop.current
// 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
}
}
// उपयोग: बैकग्रॉंड RunLoop पर टाइमर
let manager = BackgroundRunLoopManager()
manager.start()
// performSelector के माध्यम से बैकग्रॉंड RunLoop पर कार्य भेजना
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 एक टाइमर इवेंट बनाता है जिसे RunLoop BeforeTimers चरण में संसाधित करता है। टाइमर repeating (दोहराने वाले) और non-repeating (एकबार वाले) हो सकते हैं। NSTimer सड़कता की गारण्टी नहीं देता: यदि RunLoop एक लंबे ऑपरेशन द्वारा अवरुद्ध है, तो टाइमर अनब्लॉक होने के बाद चलेगा, और सभी छूटे हुए फ़ायरिंग एक में विलय हो जाएंगे (repeating टाइमर्स के लिए — अधिकतम एक “पकड़ा हुआ” फ़ायरिंग)।
CADisplayLink एक विशेषीकृत टाइमर है जो स्क्रीन रिफ़्रेश रेट (60/120/144 Hz) के साथ सिंक्रोनाइज़्ड होता है। यह एनिमेशन और वीडियो अपडेट के लिए उपयोग होता है। CADisplayLink RunLoop में जोड़ा जाता है और प्रत्येक रेंडरिंग फ्रेम से पहले चलता है (Core Animation के लेयर को रेंडरिंग के लिए भेजने से पहले)। यदि एक फ्रेम छूट जाता है (display link 16 ms के अंदर नहीं चलता), तो अगली कॉल अगले VSync साइकल में होती है।
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")
}
// KEY: .common में जोड़ें, नहीं तो स्क्रॉलिंग के दौरान टाइमर जम जाएगा
RunLoop.current.add(displayLinkTimer!, forMode: .common)
}
func stop() {
displayLink?.invalidate()
displayLinkTimer?.invalidate()
}
}
AnimationController में, CADisplayLink को .common मोड में जोड़ा गया है, जो सुनिश्चित करता है कि स्क्रॉलिंग के बावजूद हर फ्रेम पर step कॉल किया जाए। displayLink.add(to: .current, forMode: .common) — एनिमेशन के लिए मानक पैटर्न है जो स्क्रॉलिंग के दौरान बाधित नहीं होना चाहिए। NSTimer भी .common मोड में जोड़ा गया है ताकि स्क्रॉलिंग के दौरान टिक करता रहे। इसके बिना, टाइमर केवल .default मोड में ही चलता।
CFRunLoopObserver RunLoop चरणों को ट्रैक करने का एक तंत्र है। Observer के साथ, आप मोड प्रवेश, टाइमर प्रोसेसिंग की शुरुआत, स्रोत प्रोसेसिंग की शुरुआत, नींद के बाद जागना, और मोड से बाहर निकलने के बारे में सूचनाएँ प्राप्त कर सकते हैं। Observers का उपयोग फ्रेमवर्क्स द्वारा अपनी आवश्यकताओं के लिए किया जाता है: Core Animation उनका उपयोग RunLoop के सोने से पहले लेयर रेंडर करने के लिए करता है, UIKit — घटना प्रोसेसिंग के बाद लेआउट अपडेट करने के लिए।
एक डेवलपर अपने उद्देश्यों के लिए भी Observers जोड़ सकता है। उदाहरण के लिए: घटना प्रोसेसिंग समय मापना (प्रोफ़ाइलिंग), RunLoop के सोने से पहले विलंबित कार्य निष्पादित करना (जब UI पहले ही अपडेट है और उपयोगकर्ता इंटरैक्ट नहीं कर रहा है), लंबी निष्क्रियता के दौरान स्वचालित रूप से डेटा सहेजना। Observer को CFRunLoopAddObserver के माध्यम से एक मोड और ट्रैक की गई गतिविधियों के बिट मास्क के साथ पंजीकृत किया जाता है।
सबसे उपयोगी Observer बिंदु: .afterWaiting — RunLoop के जागने के बाद निष्पादित होता है और उस कोड को शामिल कर सकता है जो घटना प्रोसेसिंग के बाद चलना चाहिए; .beforeTimers — टाइमर प्रोसेसिंग से पहले, पिछले प्रोसेसिंग के बाद बीते समय को मापने की अनुमति देता है; .exit — RunLoop बंद होने पर चलता है, बैकग्रॉंड थ्रेड संसाधनों की सफाई के लिए उपयोगी।
CFRunLoopStop एक फ़ंक्शन है जो वर्तमान RunLoop पुनरावृत्ति को जबरन समाप्त करता है। जब CFRunLoopStop(CFRunLoopGetCurrent()) कॉल किया जाता है, RunLoop वर्तमान घटना को संसाधित करना समाप्त करता है और run() से false लौटाकर बाहर निकल जाता है। यह बैकग्रॉंड थ्रेड पर RunLoop रोकने का मानक तरीका है। Main Thread पर, CFRunLoopStop अनुशंसित नहीं है — मुख्य RunLoop को एप के पूरे जीवनकाल चलना चाहिए। बैकग्रॉंड थ्रेड्स के लिए, CFRunLoopStop के बाद, थ्रेड run() के बाद कोड को समाप्त या जारी रख सकता है।
अक्सर पूछे जाने वाले प्रश्न
RunLoop iOS में एक इवेंट प्रोसेसिंग साइकल है, जो CFRunLoop (Core Foundation) और NSRunLoop (Foundation) द्वारा कार्यान्वित होता है। यह घटनाओं (स्पर्श, टाइमर, इनपुट स्रोत) की प्रतीक्षा करता है और उन्हें थ्रेड पर हैंडलर्स तक भेजता है। प्रत्येक थ्रेड में एक RunLoop हो सकता है, लेकिन यह स्वचालित रूप से केवल Main Thread के लिए बनाया जाता है। RunLoop मोड (.default, .tracking, .common) का प्रबंधन करता है, प्राथमिकता के अनुसार प्रोसेसिंग को अलग करता है।
NSTimer डिफ़ॉल्ट रूप से .default RunLoop मोड में जोड़ा जाता है। जब उपयोगकर्ता स्क्रॉल करता है, RunLoop .tracking मोड में बदल जाता है और .default से टाइमर्स को संसाधित नहीं करता। समाधान: RunLoop.current.add(timer, forMode: .common) के माध्यम से टाइमर को .common मोड में जोड़ें। .common .default और .tracking दोनों को जोड़ता है, इसलिए टाइमर दोनों मोड्स में चलता है।
केवल अगर बैकग्रॉंड थ्रेड टाइमर्स (NSTimer), performSelector:onThread:, NSInputStream/NSOutputStream या स्रोत घटनाओं का उपयोग करता है। यदि थ्रेड एक सिंक्रोनस कार्य (फ़ाइल डाउनलोड, गणना) करता है और समाप्त हो जाता है — RunLoop की आवश्यकता नहीं है। शुरू करने के लिए, स्रोत कॉन्फ़िगर करने के बाद RunLoop.current.run() कॉल करें। रोकने के लिए — CFRunLoopStop(CFRunLoopGetCurrent())।
RunLoop थ्रेड स्तर पर काम करता है और मोड समर्थन के साथ घटनाओं को अनुक्रमिक रूप से संसाधित करता है। DispatchQueue एक थ्रेड पूल अबस्ट्रेक्शन है — कार्य किसी भी उपलब्ध थ्रेड पर निष्पादित होते हैं। GCD मोड का समर्थन नहीं करता और RunLoop से स्वतंत्र रूप से काम करता है। DispatchQueue.main ब्लॉक निष्पादित करने के लिए मुख्य RunLoop का उपयोग करता है — यह एकमात्र प्रतिच्छेद बिंदु है। बैकग्रॉंड कार्यों के लिए, GCD को प्राथमिकता दी जाती है।
CADisplayLink एक टाइमर है जो VSync (स्क्रीन रिफ़्रेश रेट) के साथ सिंक्रोनाइज़्ड होता है। यह RunLoop में जोड़ा जाता है और BeforeTimers चरण में प्रत्येक रेंडरिंग फ्रेम से पहले चलता है। CADisplayLink केवल Main Thread पर काम करता है, क्योंकि स्क्रीन रेंडरिंग वहीं होती है। स्क्रॉलिंग के दौरान निरंतर एनिमेशन के लिए, इसे .common मोड में जोड़ें: displayLink.add(to: .current, forMode: .common)।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें