Программирование SwiftARC и памятьРазработчик мобильных приложений на Swift

При передаче экземпляра Swift класса в Objective C API кто гарантирует его жизнь до завершения вызова?

При передаче экземпляра Swift-класса в Objective-C API кто гарантирует его жизнь до завершения вызова?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Жизнь экземпляра до завершения вызова гарантирует сама семантика передачи аргумента: вызываемый Objective-C метод получает действительный объект на всё время выполнения вызова. ARC может использовать временное заимствование вместо фактического retain, если это безопасно, поэтому наличие видимого увеличения счётчика ссылок не является обязательным.

После возврата из метода объект может быть уничтожен, если не осталось других сильных владельцев. Если Objective-C API сохраняет объект для последующего использования, оно обязано сделать собственное владение: например, сохранить сильную ссылку или скопировать объект согласно контракту API.

Исторический контекст

ARC появился как автоматизация ручного управления retain/release, характерного для Objective-C. Исходная проблема заключалась в необходимости вручную балансировать владение объектами и одновременно гарантировать, что объект не будет освобождён во время вызова.

При взаимодействии Swift и Objective-C компилятор анализирует границы вызовов и вставляет необходимые операции управления временем жизни. Оптимизации могут заменять реальные retain/release временными гарантиями жизни, не меняя наблюдаемую корректность программы.

Постановка проблемы

Рассмотрим метод, который получает объект, но не сохраняет его, а только использует внутри вызова. Если вызывающая сторона обнулит свою последнюю сильную ссылку во время подготовки или выполнения вызова, объект всё равно не должен исчезнуть до завершения обращения к нему.

Иная ситуация возникает, если Objective-C API сохраняет указатель или объект для асинхронной работы, но не увеличивает владение. Тогда после возврата метода объект может быть уничтожен, а сохранённая ссылка станет недействительной. Ошибка находится уже на стороне контракта API, а не в обычной работе ARC.

Подробное решение

На границе вызова аргумент должен оставаться валидным в течение всего времени, когда вызываемый код может им пользоваться. Это не означает, что ARC обязан выполнить отдельный сильный retain именно перед вызовом: компилятор вправе применить borrowed-передачу, если доказано, что нужная гарантия уже обеспечена другим владением.

Нужно различать два случая:

  • Использование только во время вызова. Вызываемый код может читать объект и вызывать его методы; после возврата временная гарантия заканчивается.
  • Сохранение для будущего использования. API должно установить собственное владение. Простое сохранение необладающего указателя не продлевает жизнь объекта.

ARC управляет обычными сильными ссылками Swift и корректными Objective-C соглашениями о владении, но не может исправить API, которое хранит указатель без владения. Особенно опасны такие указатели при асинхронных callback-ах, C-структурах и ручном использовании Unsafe.

Следовательно, по трассировке retain/release нельзя делать вывод, что объект обязательно получил отдельную сильную ссылку на время каждого вызова. Надёжный вывод определяется семантикой владения и действительностью объекта в рамках вызова, а не конкретной последовательностью оптимизированных инструкций.

Ситуация из практики

Контроллер передаётся в Objective-C-компонент для регистрации обработчика. Компонент использует контроллер только во время регистрации — это безопасный сценарий: вызов гарантирует валидность аргумента, а после возврата контроллер может быть уничтожен.

Если компонент сохраняет обработчик для будущего события, возможны варианты:

  • хранить контроллер сильной ссылкой — просто, но может создать цикл владения;
  • хранить слабую ссылку — цикл не возникает, но контроллер может исчезнуть до события;
  • хранить отдельный объект-адаптер с явно определённым временем жизни — сложнее, зато позволяет разделить регистрацию, отмену и владение.

Практически выбирают слабую ссылку, если событие не должно продлевать жизнь контроллера, и обеспечивают явное снятие регистрации. Если компонент обязан гарантировать доставку события, сильное владение допустимо, но граф владения нужно разорвать при завершении работы.

Что кандидаты часто упускают

  1. Обязан ли вызываемый метод делать retain при получении объекта?

Нет. Он обязан обеспечить корректность использования объекта во время вызова, но конкретный retain может быть устранён оптимизатором. Если объект требуется после возврата, метод должен явно сохранить его во владении.

  1. Продлевает ли передача объекта в Objective-C API его жизнь после возврата метода?

Нет, сама передача не создаёт долгосрочного владения. После завершения вызова объект может быть уничтожен, если у него нет других сильных ссылок. Продление жизни возникает только при сохранении объекта API с корректной семантикой владения.

  1. Что произойдёт, если Objective-C API сохранит необладающий указатель на Swift-объект?

После исчезновения последней сильной ссылки объект может быть уничтожен, и указатель станет недействительным. Последующее обращение приведёт к неопределённому поведению или аварийному завершению; ARC не отслеживает произвольные необладающие указатели. Такой API нужно адаптировать: передавать объект с явным владением, использовать безопасный callback-контракт или гарантировать, что владелец живёт дольше сохранённого указателя.