В Swift 5.5 появилась возможность использовать обертки свойств на параметрах функций и замыканий. Это наконец позволило мне реализовать то, что я и многие другие люди всегда хотели видеть в Swift — способ каким-либо образом обеспечить, чтобы замыкание (closure) вызывалось ровно один раз.
Чтобы понять, зачем, вот вам простой пример:
// упс, здесь мы забываем вызвать comletion(...)!
// … бизнес-логика
// упс, мы забыли поставить return
в операторе if
выше, поэтому выполнение функции продолжается, и мы второй раз вызываем замыкание (с неправильным результатом!).
Код, в котором у вас есть функции с обработчиком завершения, который должен вызываться с успешным или неудачным значением в зависимости от результата работы, которую выполняет функция, очень распространен.
Если вы не будете осторожны, можно легко сделать ошибку, когда вы вообще не вызовете обработчик завершения или вызовете его более одного раза, особенно если в функции присутствует сложная бизнес-логика или обработка ошибок.
Каюсь, я сам совершал эту ошибку много раз, и иногда это бывает достаточно сложно или требует много времени — точно выяснить, почему ваш код работает не так, как ожидалось. После небольшой отладки и просмотра кода вы понимаете: «О! Я забыл вызвать обработчик завершения!» или «О! Я забыл проставить return
!» и вы довольные идете и исправляете свой код. Все хорошо!
Хотя было бы неплохо, если бы ваш код мог просигнализировать вам: «Эй! Вы забыли вызвать это замыкание!»? Вот где пригодится обертка свойств @Once
.
Вот наивная реализация:
Вы можете аннотировать параметр своей функции с помощью этой обертки свойства:
И теперь вы получите ошибку во время выполнения, если замыкание не было вызвано в теле ровно один раз!
Не забывайте, что вы все еще можете использовать эту обертку свойства вне параметров функции или замыкания, то есть на локальных свойствах или свойствах инстанса, которые имеют тип замыкания, как вы уже это делали в более старых версиях Swift!
Одним из ограничений этой обертки свойств является то, что она не работает с изолирующими (non-escaping) замыканиями, поскольку замыкание необходимо сохранить внутри обертки свойства для последующего выполнения. Невозможно доказать компилятору, что замыкание не сбежит, даже если вы точно знаете, что этого не произойдет (возможно, потому, что ваш код полностью синхронен). Поэтому вам нужно будет аннотировать ваши изолирующие замыкания @escaping
, чтобы использовать эту технику, что на практике вам не совсем нужно, но, к сожалению, является единственным обходным путем.
Вы можете найти на GitHub полный код, который поддерживает как пробрасывающие (throwing), так и не пробрасывающие типы функций. Код доступен в виде пакета Swift, который при необходимости можно легко добавить в кодовую базу.
Материал подготовлен в рамках курса "iOS Developer. Professional".
Всех желающих приглашаем на двухдневный онлайн-интенсив «Пишем современное iOS приложение на SwiftUI». В первый день разберем особенности создания UI с помощью данного фреймворка. Во второй — напишем бизнес-логику с помощью нативных средств (Combine). Также будем использовать новинки, представленные на WWDC 2021, в том числе и async-await. Вебинар рассчитан на iOS разработчиков, уже имеющих опыт разработки iOS приложений и желающих познакомиться со SwiftUI. Также будет полезно тем, кто уже разрабатывает на SwiftUI, но хотел бы узнать о новинках. РЕГИСТРАЦИЯ