האפליקציה שלך ל-iOS קורסת ללא לוג קריסה? משתמשים מתלוננים, אבל Crashlytics שותק. ככל הנראה נתקלת ב-Watchdog Termination — מנגנון מערכתי שמסיים בכוח את פעולת האפליקציה אם ה-thread הראשי נתקע מעבר לסף (כ-8 שניות בגרסאות iOS ישנות יותר, ~4 שניות בחדשות יותר). אנו מגדירים זיהוי לאירועים כאלה כדי שתקבל אבחון מלא ותוכל לחסל את שורש הבעיה. לאורך השנים, זיהינו עשרות פרויקטים שבהם Watchdog Terminations לא זכו לתשומת לב, למרות שהיוו 2–3% מכלל הסשנים. הגדרת Sentry הבסיסית שלנו עולה $500–$1,000 עם התקנה תוך 4–8 שעות, ויכולה לחסוך עד 5 שעות בשבוע בניתוח אירועים. עם ניסיון של 6+ שנים בפיתוח iOS ומעל 60 פרויקטי ניטור, אנו מבטיחים הגדרה איכותית.
למה מדווחי קריסה סטנדרטיים מפספסים Watchdog Termination
Firebase Crashlytics אינו רושם Watchdog Termination — זה לא exception ולא signal. Sentry מגרסה 8.0 יכול לזהות זאת באמצעות דגלים ב-UserDefaults (הפעל enableWatchdogTerminationTracking). MetricKit מספק נתונים מדויקים אך עם עיכוב של עד יום. בחירת השיטה תלויה בעדיפויות שלך: מהירות מול דיוק. Apple Documentation: MXHangDiagnostic הוא המקור הרשמי היחיד ל-stack trace מדויק של ה-thread הראשי בזמן התקיעה.
| שיטה | מהירות קבלת הנתונים | דיוק ה-stack | עלויות נוספות |
|---|---|---|---|
| MetricKit | עד 24 שעות | גבוה (callStackTree) | חינם (מובנה ב-iOS) |
| Sentry | דקות | בינוני (דגל + stack של ה-thread הראשי) | מנוי ל-sentry.io |
| גלאי מותאם אישית | זמן אמת | גבוה (BSBacktraceLogger) | פיתוח ותחזוקה |
למעקב יעיל אחר סיום כפוי של אפליקציות iOS, שלבו את MetricKit ו-Sentry כדי לנטר תקיעות של ה-thread הראשי.
איך לבחור שיטת ניטור?
אם אתה צריך דיוק מקסימלי לניתוח מעמיק, השתמש ב-MetricKit. זה חינם, אבל הנתונים מגיעים באיחור, מה שהופך אותו ללא מתאים לתגובה מהירה. אם מהירות חשובה, Sentry מספק מידע תוך דקות עם דיוק מספק. כשאתה צריך ניטור בזמן אמת וסף הפעלה נמוך (2–3 שניות), אנו בונים גלאי מותאם אישית המבוסס על DispatchQueue.main.async עם משוב דרך BSBacktraceLogger. Sentry מהיר פי 2 מ-MetricKit בקבלת נתונים, אבל דיוק ה-stack נמוך יותר בגלל היעדר callStackTree.
איך אנחנו מגדירים ניטור: מקרה אמיתי
הלקוח שלנו מתחום הפינטק נתקל ב-Watchdog Terminations המוניים לאחר עדכון אפליקציה. המהנדסים חיברו את Sentry עם סף של appHangTimeoutInterval = 2.5 שניות וגילו שב-70% מהמקרים, התקיעה התרחשה במתודת processTransaction ב-thread הראשי עקב כתיבת CoreData סינכרונית. העברנו את הכתיבה ל-context רקע — תדירות האירועים ירדה ב-90%. הגדרנו בנוסף את MetricKit לאישור — הנתונים תאמו.
המלצות לבחירת סף ההפעלה
לאפליקציות עם ממשקים כבדים (למשל, אנימציות), יש להוריד את הסף ל-2–3 שניות. לאפליקציות פיננסיות, שבהן כל אלפית שנייה חשובה, 1–2 שניות. השתמש בבדיקות A/B לפני ההשקה.
יישום גלאי מותאם אישית (אם אתה צריך מהירות)
final class WatchdogDetector {
private let queue = DispatchQueue(label: "watchdog.monitor", qos: .utility)
private var pingTime: Date = Date()
private let threshold: TimeInterval = 3.0
func start() {
scheduleMainThreadPing()
scheduleBackgroundCheck()
}
private func scheduleMainThreadPing() {
DispatchQueue.main.async { [weak self] in
self?.pingTime = Date()
self?.scheduleMainThreadPing()
}
}
private func scheduleBackgroundCheck() {
queue.asyncAfter(deadline: .now() + 1.0) { [weak self] in
guard let self = self else { return }
let elapsed = Date().timeIntervalSince(self.pingTime)
if elapsed > self.threshold {
self.captureHang(duration: elapsed)
}
self.scheduleBackgroundCheck()
}
}
private func captureHang(duration: TimeInterval) {
// Используем BSBacktraceLogger для снятия стека main thread
BacktraceLogger.backtrace(for: .main) { frames in
SentrySDK.capture(error: NSError(
domain: "WatchdogHang",
code: Int(duration * 1000),
userInfo: [
NSLocalizedDescriptionKey: "Main thread hung for \(duration)s",
"stackFrames": frames
]
))
}
}
}final class WatchdogDetector { private let queue = DispatchQueue(label: "watchdog.monitor", qos: .utility) private var pingTime: Date = Date() private let threshold: TimeInterval = 3.0 func start() { scheduleMainThreadPing() scheduleBackgroundCheck() } private func scheduleMainThreadPing() { DispatchQueue.main.async { [weak self] in self?.pingTime = Date() self?.scheduleMainThreadPing() } } private func scheduleBackgroundCheck() { queue.asyncAfter(deadline: .now() + 1.0) { [weak self] in guard let self = self else { return } let elapsed = Date().timeIntervalSince(self.pingTime) if elapsed > self.threshold { self.captureHang(duration: elapsed) } self.scheduleBackgroundCheck() } } private func captureHang(duration: TimeInterval) { // Используем BSBacktraceLogger для снятия стека main thread BacktraceLogger.backtrace(for: .main) { frames in SentrySDK.capture(error: NSError( domain: "WatchdogHang", code: Int(duration * 1000), userInfo: [ NSLocalizedDescriptionKey: "Main thread hung for \(duration)s", "stackFrames": frames ] )) } } } לוכד את ה-stack trace רק של ה-thread הנוכחי. עבור ה-thread הראשי, השתמש ב-BSBacktraceLogger או PLCrashReporter.
מה כוללת ההגדרה (תוצרים)
- התקנה והגדרה של מנוי MetricKit לקבלת MXHangDiagnostic
- אינטגרציה של Sentry עם
Thread.callStackSymbolsמופעל וסף מותאם - במידת הצורך, פיתוח גלאי מותאם אישית עם סף נמוך (מ-2 שניות)
- הגדרת התראות ב-Sentry או במערכת הניטור שלך לעלייה ב-Watchdog Termination Rate
- ניתוח callStackTree וזיהוי צווארי בקבוק (פעולות סינכרוניות ב-thread הראשי)
- תיעוד של הגדרת הניטור והוראות אינטגרציה
- גישה ללוחות מחוונים של ניטור ולהגדרות
- הדרכה לצוות שלך (עד שעתיים)
- תמיכה למשך חודש לאחר ההשקה
מקורות טיפוסיים ל-Watchdog Termination (ומה לעשות בנידון)
| בעיה | פתרון |
|---|---|
| קריאת CoreData סינכרונית ב-viewDidLoad | העבר את הבקשה ל-context רקע |
| DispatchSemaphore.wait() ללא timeout ב-thread הראשי | השתמש ב-async-await או ב-timeout |
| Deadlock בין @MainActor לקוד Swift Concurrency סינכרוני | הימנע מחסימת ה-actor |
| פענוח JSON כבד ב-closure של URLSession | החלף thread באמצעות dataTask עם qos: .userInitiated |
תהליך העבודה
- ניתוח — אנו חוקרים את הארכיטקטורה הנוכחית, גרסאות iOS, תרחישים טיפוסיים. אנו אוספים מדדים דרך MetricKit (אם כבר נעשה בו שימוש).
- עיצוב — אנו בוחרים את הסט האופטימלי: MetricKit + Sentry / Sentry בלבד / מותאם אישית + Sentry. אנו קובעים ספי הפעלה.
- יישום — אנו כותבים ומשלבים קוד, בודקים על סימולטור ומכשירים אמיתיים עם גרסאות iOS שונות.
- בדיקות — אנו מדמים תקיעות (למשל, דרך
enableWatchdogTerminationTrackingב-thread הראשי) ומוודאים שהגלאי מופעל ושולח אבחון. - השקה — אנו מפיצים דרך TestFlight, עוקבים אחר נתונים ראשוניים ומתאימים ספים לפי הצורך.
לוח זמנים ועלות
הגדרה בסיסית דרך Sentry: 4–8 שעות ($500–$1,000). אינטגרציה של MetricKit עם שליחת אבחון לשרת שלך: 1–2 ימים ($1,500–$3,000). מחזור מלא עם גלאי מותאם אישית וניתוח: מ-3 ימים ($4,000–$7,000). העלות מחושבת באופן אישי — צור קשר להערכת פרויקט. קבל ייעוץ — נעזור להפחית את תדירות ה-Watchdog Termination ולשפר את חוויית המשתמש. עם ניסיון של 6+ שנים בפיתוח iOS ו-מעל 60 פרויקטי ניטור, אנו מבטיחים הגדרה איכותית.







