Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni
🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!23.09.2024
Семафори
🚥 Подолав сьогодні найскладніший для рівночасності модуль. То є мій порт GIF2MP4.swift, якій побудований на бібліотеці AVFoundation
. Вона ще не була перероблена не тільки на async/await
, а, здається, навіть на Swift (бо більшість бібліотек Apple для Swift почали життя як прямий порт з Objective C, а потім були переосмислені на ідіоматичний Swift.)
Проблемний код був навколо класу AVAssetWriterInput
- він збирає відео по кадрах. Зрозуміло, що це споживає багато ресурсів, тому кадри ми передаємо в міру потреби. Для того є метод requestMediaDataWhenReady. Умовно, ми передаємо йому “тіло циклу”, який генеруватиме кадри.
Все б добре, але у Swift 6 більше не можна передати в той блок ані джерело кадрів, ані навіть змінну-лічильник — нічого змінного. Дилема: як зробити цикл без стану?
Я придумав інвертувати цю інверсію керування. В мене тепер requestMediaDataWhenReady
тільки перемикає семафор. А в головному методі звичайний цикл по кадрах, з одним нюансом: перед ітерацією чекає на семафор.
Вийшло так. Мені навіть подобається! Причому головний метод є синхронним та не потребує колбеку. Нагадує Ruby. Звісно, цей метод блокує аж поки не перекодує все відео, тож він має сенс тільки всередині рівночасного коду (а мені тільки це і треба).
(До речі, ці обʼєкти DispatchSemaphore можна передавати в інший потік, що дуже логічно. А ще є Atomic - контейнер для передачі значень.)
А ще цікаво, що код з семафорами повільніше за async/await
, оскільки у Swift await здатний робити “синхронний” виклик - якщо є можливість — а семафор завжди є повільною зміною контексту. Ну, у моєму випадку рішення з async/await
неможливе, тож маємо що маємо.