RunLoop — iOS میں ایونٹ پروسیسنگ کا ایک سائیکل ہے جسے CFRunLoop (Core Foundation) اور NSRunLoop (Foundation) آبجیکٹس کے ذریعے لاگو کیا جاتا ہے۔ یہ ایک میکانزم ہے جو ایونٹس (ٹچ، ٹائمرز، ان پٹ ذرائع، نوٹیفیکیشنز) کا انتظار کرتا ہے اور انہیں تھریڈ پر متعلقہ ہینڈلرز کو بھیجتا ہے۔ iOS میں ہر تھریڈ میں زیادہ سے زیادہ ایک RunLoop ہوتا ہے، لیکن یہ خودکار طور پر صرف Main Thread کے لیے بنایا جاتا ہے۔ Apple CFRunLoop Documentation کے مطابق، 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 لوپ سے بنیادی فرق ہے جو 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 پر ایک آبزرور رجسٹر کرتا ہے جو سائیکل کے ہر مرحلے کو لاگ کرتا ہے۔ یہ ڈیبگنگ کے لیے مفید ہے: اگر آپ .beforeTimers اور .afterWaiting کے درمیان طویل وقفہ دیکھتے ہیں، تو اس کا مطلب ہے کہ RunLoop Main Thread پر کسی آپریشن سے بلاک ہے۔ timerOnMainRunLoop دکھاتا ہے کہ NSTimer مرکزی RunLoop پر خودکار طور پر کیسے کام کرتا ہے — Timer.scheduledTimer بناتے وقت، ٹائمر موجودہ RunLoop میں ڈیفالٹ موڈ (.default mode) میں شامل ہو جاتا ہے۔
Android Looper — RunLoop کا ہم پلہ ہے۔ Looper.prepare() تھریڈ پر ایک پیغام قطار (MessageQueue) بناتا ہے، Looper.loop() لامتناہی پروسیسنگ لوپ شروع کرتا ہے۔ Handler اس قطار میں پیغامات اور Runnable بھیجتا ہے۔ بنیادی فرق: RunLoop موڈز (modes) کو سپورٹ کرتا ہے، جبکہ 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 کو فعال رکھنے کے لیے ایک خالی Port شامل کرتے ہیں
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 ms)
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 میں شامل کرتے ہیں، ورنہ ٹائمر اسکرول پر جم جائے گا
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 کے مراحل کی نگرانی کا ایک میکانزم ہے۔ آبزرور کی مدد سے موڈ میں داخلے، ٹائمرز پروسیسنگ کے آغاز، ذرائع پروسیسنگ کے آغاز، نیند کے بعد جاگنے، موڈ سے باہر نکلنے کے بارے میں اطلاع حاصل کی جا سکتی ہے۔ آبزرورز فریم ورکس کے ذریعے اپنی ضروریات کے لیے استعمال ہوتے ہیں: Core Animation انہیں RunLoop کے سونے سے پہلے لیئرز رینڈر کرنے کے لیے استعمال کرتا ہے، UIKit — ایونٹس پروسیس کرنے کے بعد لے آؤٹ اپ ڈیٹ کرنے کے لیے۔
ڈیولپر اپنے مقاصد کے لیے بھی آبزرورز شامل کر سکتا ہے۔ مثال کے طور پر: ایونٹ پروسیسنگ کا وقت ناپنا (پروفائلنگ)، RunLoop کے سونے سے پہلے موخر آپریشنز انجام دینا (جب UI اپ ڈیٹ ہو چکا ہو اور صارف تعامل نہ کر رہا ہو)، طویل غیر فعالیت پر خودکار ڈیٹا سیو کرنا۔ آبزرور CFRunLoopAddObserver کے ذریعے موڈ اور مانیٹر کی جانے والی سرگرمیوں کے بٹ ماسک کی وضاحت کے ساتھ رجسٹر کیا جاتا ہے۔
آبزرور کے لیے سب سے مفید پوائنٹس: .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 ڈیفالٹ طور پر RunLoop کے .default موڈ میں شامل ہوتا ہے۔ جب صارف اسکرول کرتا ہے، RunLoop .tracking موڈ میں تبدیل ہو جاتا ہے اور .default کے ٹائمرز کو پروسیس نہیں کرتا۔ حل: ٹائمر کو RunLoop.current.add(timer, forMode: .common) کے ذریعے .common موڈ میں شامل کریں۔ .common .default اور .tracking کو یکجا کرتا ہے، لہذا ٹائمر دونوں موڈز میں کام کرتا ہے۔
صرف اس صورت میں جب بیک گراؤنڈ تھریڈ ٹائمرز (NSTimer)، performSelector:onThread:، NSInputStream/NSOutputStream یا Source ایونٹس استعمال کرے۔ اگر تھریڈ سنکروس کام (فائل ڈاؤن لوڈ، حساب) انجام دے کر ختم ہو جائے — 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 ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں