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

Можно ли сделать слабую ссылку на поле типа протокол?

1.8 Middle🔥 181 комментариев
#CI/CD и инструменты разработки

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Можно ли сделать слабую ссылку на поле типа протокол?

Короткий ответ: Да, можно, но с важными нюансами. Прямое объявление weak var delegate: SomeProtocol? не всегда работает из-за ограничений системы типов Swift. Однако существуют обходные решения, которые позволяют достичь аналогичного поведения.

Почему возникают сложности?

В Swift протоколы могут быть использованы как типы, но у них есть фундаментальное ограничение: они не могут напрямую соответствовать требованию weak, поскольку weak — это модификатор, применимый только к ссылочным типам (классам), а протокол по умолчанию может быть принят и структурами, и перечислениями (значимыми типами). Компилятор не может гарантировать, что значение протокола будет ссылкой, поэтому запрещает прямое использование weak.

Пример некорректного кода:

protocol MyDelegate {
    func didSomething()
}

class MyClass {
    weak var delegate: MyDelegate? // Ошибка: 'weak' cannot be applied to non-class type 'MyDelegate'
}

Решения для создания слабой ссылки на протокол

1. Ограничение протокола только классами

Наиболее распространённый способ — использовать class или AnyObject в наследовании протокола. Это гарантирует, что протокол могут принимать только классы, что позволяет использовать weak.

protocol MyDelegate: AnyObject {
    func didSomething()
}

class MyClass {
    weak var delegate: MyDelegate? // Теперь корректно
}

Это стандартный подход, особенно для делегатов и обратных вызовов, чтобы избежать циклов сильных ссылок.

2. Использование обёрток (Wrappers)

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

struct WeakRef<T: AnyObject> {
    weak var value: T?
}

protocol MyDelegate {
    func didSomething()
}

class MyClassDelegate: MyDelegate {
    func didSomething() { print("Done") }
}

class MyClass {
    private var delegateRef: WeakRef<MyClassDelegate>?
    
    func setDelegate(_ delegate: MyClassDelegate) {
        delegateRef = WeakRef(value: delegate)
    }
    
    func notifyDelegate() {
        delegateRef?.value?.didSomething()
    }
}

Этот подход менее удобен, но обеспечивает гибкость.

3. Дженерики с ограничением класса

Вместо протокола можно использовать дженерик с ограничением AnyObject, что также позволяет применить weak.

class MyClass<T: AnyObject> {
    weak var delegate: T?
}

Ключевые моменты

  • Weak ссылки необходимы для предотвращения циклов сильных ссылок (retain cycles), особенно в паттернах типа делегации или замыканий.
  • Протоколы по умолчанию не являются ссылочными типами, поэтому weak неприменим напрямую.
  • Использование AnyObject — идиоматический способ в Swift, но он ограничивает протокол только классами. Для pure value-семантики (например, в SwiftUI) это может быть неприемлемо.
  • В многопоточных средах weak-ссылки могут стать nil в любой момент, поэтому всегда следует использовать опциональную проверку.

Практический пример

Допустим, у нас есть ViewController, который использует делегат для обработки событий:

protocol DataLoaderDelegate: AnyObject {
    func dataDidLoad(_ data: [String])
}

class DataLoader {
    weak var delegate: DataLoaderDelegate?
    
    func loadData() {
        let data = ["A", "B", "C"]
        delegate?.dataDidLoad(data) // Safe call, delegate может быть nil
    }
}

class ViewController: UIViewController, DataLoaderDelegate {
    let loader = DataLoader()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        loader.delegate = self
        loader.loadData()
    }
    
    func dataDidLoad(_ data: [String]) {
        print(data)
    }
}

Здесь weak var delegate предотвращает утечку памяти, так как ViewController владеет DataLoader, а DataLoader имеет слабую ссылку на делегат.

Вывод

Сделать слабую ссылку на поле типа протокол можно, но только если протокол ограничен классом (AnyObject). Это осознанное проектировочное решение, которое балансирует между гибкостью протоколов и управлением памятью. Если протокол должен поддерживать value-типы, потребуются дополнительные абстракции, такие как обёртки. В реальной iOS-разработке первый подход с AnyObject встречается наиболее часто, особенно в коде, взаимодействующем с UIKit/AppKit.

Можно ли сделать слабую ссылку на поле типа протокол? | PrepBro