Стендап Сьогодні 📢 Канал в 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 неможливе, тож маємо що маємо.