Докажите, что главный поток заблокирован при 400-мс зависании UI, и чем
Тап по строке замораживает UI на ~400 мс. Докажите, что главный поток заблокирован, и определите, чем именно — инструментами, а не догадками.
Ниже самый тяжёлый стек, снятый на главном потоке в окне зависания в Time Profiler. Скажите, какой сигнал подтверждает блокировку и какой фрейм за неё отвечает.
Thread 0 (main) — 396 ms в этом стеке во время зависания
-[RowController didSelect:]
DataStore.loadAll()
+[NSJSONSerialization JSONObjectWithData:options:error:]
__read_nocancel libsystem_kernel 380 ms
Определите причину.
Подтвердите инструментом, а не догадкой — Main Thread Checker или hang report помечает столл, а стек Thread 0 в Time Profiler показывает ~396 мс в одном синхронном вызове. Здесь DataStore.loadAll() парсит JSON на главном потоке, заблокирован на диске.
- ✗Гадают о причине вместо подтверждения столла инструментом
- ✗Считают заблокированным фоновый поток, когда застрял стек главного
- ✗Винят тяжёлый рендеринг или утечку вместо синхронного вызова на главном потоке
- →Какой сигнал в Time Profiler говорит, что главный поток стоял, а не работал?
- →Почему лист
__read_nocancelдоказывает, что блокировка — это дисковый I/O?
Two signals prove it, and neither is a guess. Main Thread Checker (or an Xcode hang report) fires on a main-thread stall; Time Profiler then shows the heaviest stack sitting on Thread 0 for ~396 of the 400 ms — the main thread is in one call, not scheduling frames. The leaf is the proof:
-[RowController didSelect:] ← tap handler, main thread
DataStore.loadAll() ← reads a file, then parses JSON — synchronously
__read_nocancel (kernel) ← 380 ms blocked in a disk read
__read_nocancel is a blocking kernel read: the main thread is parked on disk I/O, not doing work. The fix is to move the load off the main thread and hand the result back:
Task.detached(priority: .userInitiated) {
let items = try await DataStore.loadAllAsync() // read + parse off-main
await MainActor.run { self.apply(items) }
}