← Назад к вопросам

Почему лучше не использовать Unowned?

2.0 Middle🔥 181 комментариев
#Управление памятью#Язык Swift

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему избегать Unowned в современном Swift

Unowned ссылка казалась хорошей идеей, но на практике она опасна и может привести к крашам. Вот почему современные разработчики предпочитают weak.

Что такое Unowned

Unowned ссылка похожа на weak, но:

  • НЕ опциональна
  • НЕ становится nil при удалении объекта
  • Может вызвать CRASH если объект удалён
class CreditCard {
    let owner: Person  // Требует Person
    unowned let parentCard: CreditCard?  // Ошибка: не может быть опциональным!
    
    init(owner: Person) {
        self.owner = owner
    }
}

Главная проблема: CRASH при dangling reference

Если объект удалён, а unowned ссылка на него используется — приложение КРАШИТСЯ.

class Person {
    let name: String
    init(name: String) { self.name = name }
    deinit { print("Person deinitialized") }
}

class Pet {
    unowned let owner: Person
    
    init(owner: Person) {
        self.owner = owner
    }
    
    func printOwnerName() {
        print(owner.name)  // CRASH если owner был удалён!
    }
}

var pet: Pet?
do {
    let person = Person(name: "Alice")
    pet = Pet(owner: person)
}  // person удалён отсюда

pet?.printOwnerName()  // CRASH: Thread 1: EXC_BAD_ACCESS

Vs. Weak (безопасно):

class Pet {
    weak var owner: Person?  // Опциональна, не крашится
    
    init(owner: Person) {
        self.owner = owner
    }
    
    func printOwnerName() {
        if let owner = owner {
            print(owner.name)
        } else {
            print("Owner was deallocated")
        }
    }
}

var pet: Pet?
do {
    let person = Person(name: "Alice")
    pet = Pet(owner: person)
}

pet?.printOwnerName()  // Печатает "Owner was deallocated", без крашей

Проблема 1: Непредсказуемость в asyncly-concurrent коде

В современном Swift с async/await, объекты могут удалиться в неожиданный момент.

class DataManager {
    unowned let delegate: UIViewController
    
    init(delegate: UIViewController) {
        self.delegate = delegate
    }
    
    func fetchData() async {
        let data = try await apiService.fetch()
        // К этому моменту UIViewController мог быть popped с stack'а!
        self.delegate.displayData(data)  // CRASH
    }
}

Безопасно с weak:

class DataManager {
    weak var delegate: UIViewController?
    
    func fetchData() async {
        let data = try await apiService.fetch()
        guard let delegate = delegate else { return }
        delegate.displayData(data)
    }
}

Проблема 2: Трудно понять lifecycle

У unowned нет явного механизма отслеживания. Нужно помнить в голове:

  • "Владеет ли этот объект целевым объектом?"
  • "Может ли целевой объект быть удалён?"
class ViewControler: UIViewController {
    let dataManager: DataManager
    
    init() {
        // Кто владеет кем? Это не очевидно
        self.dataManager = DataManager(delegate: self)
    }
}

Лучше быть явным:

class ViewController: UIViewController {
    let dataManager: DataManager
    
    init() {
        self.dataManager = DataManager()
        self.dataManager.delegate = self  // Явно weak
    }
}

Проблема 3: Сложно debug'ить

Когда приложение крашится на unowned ссылке, стек вызовов часто указывает на странное место.

Thread 1: EXC_BAD_ACCESS (code=1, address=0x...)
Frame 0: Pet.printOwnerName() + 42
Frame 1: ... [запутанный стек]

Это затрудняет поиск реальной проблемы.

Проблема 4: Неправильное использование в замыканиях

Unowned в capture list'е closure'а может привести к трудноуловимым ошибкам:

class ViewController: UIViewController {
    var completion: (() -> Void)?
    
    func setupCompletion() {
        completion = { [unowned self] in
            self.updateUI()  // CRASH если ViewController был удалён
        }
    }
}

var vc: ViewController? = ViewController()
vc?.setupCompletion()
vc = nil  // ViewController удалён
// Если кто-то вызовет completion — CRASH

Безопаснее:

completion = { [weak self] in
    guard let self else { return }
    self.updateUI()
}

Когда Unowned всё ещё используется

Unowned имеет несколько валидных use case'ов:

1. Очень чёткий parent-child relationship

class TreeNode {
    var children: [TreeNode] = []
    unowned let parent: TreeNode?  // Родитель всегда существует
    
    init(parent: TreeNode) {
        self.parent = parent
        parent.children.append(self)
    }
}

Здесь мы АБСОЛЮТНО УВЕРЕНЫ что parent существует пока существует child.

2. В performance-critical коде

class HighPerformanceCache {
    unowned let owner: GameScene  // Нужна максимальная скорость
}

Но даже тут нужна осторожность.

Современный подход: Weak everywhere

В Swift 5.7+ рекомендуется просто использовать weak везде:

class Service {
    weak var delegate: Delegate?  // Просто weak
    
    func execute() async {
        guard let delegate = delegate else { return }
        // Безопасно вызываем
        await delegate.didFinish()
    }
}

Альтернатива: Combine и Publisher'ы

Вместо delegate pattern'а с unowned/weak, используй Combine:

class Service {
    let resultPublisher = PassthroughSubject<String, Never>()
    
    func execute() async {
        let result = try await fetchData()
        resultPublisher.send(result)
        // Нет проблем с lifecycle!
    }
}

class ViewController: UIViewController {
    let service = Service()
    var cancellables: Set<AnyCancellable> = []
    
    func setup() {
        service.resultPublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] result in  // Даже если weak — нет крашей
                self?.displayResult(result)
            }
            .store(in: &cancellables)
    }
}

Вывод и рекомендации

ИЗБЕГАЙТЕ unowned потому что:

  1. Крашится приложение без warning'ов
  2. Сложно отследить в async коде
  3. Непредсказуемо в многопоточности
  4. Трудно debug'ить
  5. Нет преимуществ over weak

ИСПОЛЬЗУЙТЕ вместо этого:

  • weak var с guard let проверками
  • Combine/Reactive паттерны
  • Явное управление lifecycle'ом
  • Dependency injection паттерны

В современном Swift:

  • async/await делает unowned ещё более опасным
  • weak является безопасным выбором по умолчанию
  • Сложность управления памятью переместилась в async граничные условия

Если разработчик хочет использовать unowned, это должно быть с явным комментарием и абсолютной уверенностью в lifecycle'е.