Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni

🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!

18.12.2024

Дев-адвент 18: життєвий цикл у SwiftUI

#Адвент2024 #SwiftUI

😵‍💫 Розбирався з ситуацією, що після зміни налаштувань не оновлюються запити, які ті налаштування зчитують. (Не з бази зчитують, а зі змінних.) Для того довелося все ж вивчити життєвий цикл компонент у SwiftUI. Знайшов прегарну книжку Thinking in SwiftUI (це справжній “посібник, який забули покласти”!) та з її допомогою вдалося второпати.

(Примітка. Я звик до реактивного UI в моделі React, особливо — з хуками. Все ще вважаю, що цей API, разом з лінтером для Rules of Hooks, найкращий у своєму класі. Там в мене ніколи не стає питання, що саме буде виконано при рендері, коли будуть викликані хуки, та який стан є тимчасовим, а який — сталим. У SwiftUI не все так гарно.)

😶‍🌫️ У SwiftUI компоненти є нащадками структури View. Недосвідченому у Swift програмісту може здатись, що наявність структури робить компонент сталою сутністю, бо це “щось на кшталт класу”. Але така інтуїція неправильна, бо навпаки, структура у Swift - це “тип-значення”, а відповідно, нащадки View не здатні зберігати стан.

🏭 Натомість кожен View є “фабрикою” для компонент. Центром її є атрибут body - він аналогічний до функціональної компоненти в React. А все, що оголошено поза body, є допоміжною логікою. Головним чином, ця логіка встановлює привʼязку до стану. Як і в React, стан зберігається… “десь там”, а ми до нього можемо звернутися — обгорткою @State тощо. Важливо памʼятати, що весь зміст View - це тільки аргументи до функції рендеру body, та вони не є сталими.

Ось такий цікавий патерн Swift - структура є фактично замиканням навколо функції та її параметрів. А тільки що відкрив вікіпедію та побачив, що таке використання буквально називається замиканням! А я-то думав, що замиканням може бути тільки функція.

💣 А тепер, підступ. Під час виконання конструктора View ще не має доступу до реального стану! Бо це ж тільки фабрика будується, а не компонент. Тому в конструкторі можна задати тільки початковий стан @State, або, наприклад @Query з GRDB. Це зовсім не очевидно, бо конструктор дійсно викликається на кожну перемальовку, та в ньому можна присвоїти значення для змінної @State, але працювати воно не буде. Та ані компілятор, ані лінтер не попереджають.

🪨 В моєму випадку з запитами знайшовся режим Query(constant:in:). В ньому, як я це розумію, замість стану, який можна змінювати, запит стає “константою” - а це значить, парадоксально, що коли його параметри в конструкторі змінюються, то й результат теж — а не тільки початковий стан. Таке.