Сравнение: влияет ли объявление класса как final на время жизни его экземпляров под управлением ARC?
Нет. final запрещает наследование класса, но не изменяет правила владения, подсчёта ссылок и момент, когда ARC может уничтожить экземпляр. Объект всё равно живёт, пока существует хотя бы одна сильная ссылка.
Модификатор final решает задачу наследования и диспетчеризации вызовов: он сообщает компилятору, что класс нельзя расширить через подкласс. Это позволяет безопаснее выполнять некоторые оптимизации, например статически выбирать реализацию метода, но final не является квалификатором владения.
ARC отвечает за управление временем жизни ссылочных объектов, а final — за ограничения на иерархию типов. Эти механизмы независимы.
Неверно считать, что экземпляр final-класса освобождается раньше, потому что компилятор лучше знает его структуру. Если экземпляр удерживается сильным свойством, коллекцией, замыканием или другой ссылкой, объявление класса как final не позволяет ARC уничтожить его раньше.
Практическая ошибка возникает, когда разработчик делает класс final в надежде устранить утечку памяти. Цикл сильных ссылок при этом сохранится, если не изменить сам граф владения.
При создании экземпляра сильная ссылка увеличивает его владение, а при исчезновении последней сильной ссылки объект становится доступным для уничтожения. Для этого алгоритма неважно, допускает ли класс наследование.
final может косвенно улучшить производительность вызовов за счёт деvirtualизации и других оптимизаций компилятора. Однако такие оптимизации не должны менять наблюдаемую семантику: deinit не может быть вызван только потому, что класс объявлен как final.
После присваивания first = nil экземпляр сохраняется ссылкой second. Только исчезновение second делает объект доступным для уничтожения. Если бы класс не был final, правила ARC были бы теми же.
Контроллер хранит замыкание, которое сильно захватывает сам контроллер. Разработчик объявляет контроллер как final, ожидая, что это устранит утечку.
Вариант с сохранением final без изменения владения не решает проблему: сильная ссылка контроллера на замыкание и сильный захват self по-прежнему образуют цикл. Удаление final также ничего не меняет в управлении памятью и может лишь ухудшить возможности оптимизации диспетчеризации.
Правильное решение — оставить final, если наследование не требуется, а цикл разорвать подходящим захватом, обычно weak или unowned с доказанным временем жизни. Выбор ссылки определяется графом владения и контрактом времени жизни, а не тем, является ли класс final.
final-класс участвовать в цикле сильных ссылок?Да. Запрет наследования никак не запрещает классу хранить замыкание, которое сильно захватывает этот же экземпляр, или участвовать в цикле через другие объекты. ARC не обнаруживает такой цикл как недостижимый граф и не освобождает его автоматически.
final привести к более раннему вызову deinit благодаря оптимизации?Нет в смысле наблюдаемой семантики программы. Компилятор может оптимизировать операции удержания и освобождения, но не вправе уничтожить объект, пока это нарушало бы гарантированную доступность объекта в нужном месте. Для принудительного продления жизни до конкретной операции применяют явные средства вроде withExtendedLifetime, а не final.
final размещение экземпляра на стеке?Нет. final запрещает подклассы, но экземпляр класса остаётся ссылочным объектом с семантикой идентичности и ARC-владения. Компилятор теоретически может выполнить оптимизацию размещения, если это безопасно, однако такая оптимизация не является гарантией языка и не меняет модель времени жизни объекта.