Как объявить массив слабых ссылок (weak references) в Swift?
Проблема и её корень
В Swift weak может применяться только к опциональным переменным ссылочного типа (классы или class-bound протоколы). Массив — это структура, поэтому напрямую объявить weak var array: [SomeClass] нельзя. Однако есть несколько элегантных решений.
Решение 1: Обёртка WeakRef (самый популярный подход)
Создаём generic-структуру, которая хранит слабую ссылку:
class Delegate {
var name: String
init(name: String) { self.name = name }
deinit { print("\(name) deallocated") }
}
struct WeakRef<T: AnyObject> {
weak var value: T?
init(_ value: T) { self.value = value }
}
var delegates = [WeakRef<Delegate>]()
let d1 = Delegate(name: "First")
delegates.append(WeakRef(d1))
// Когда d1 будет освобождён, его ссылка в массиве станет nil
print(delegates.compactMap { $0.value }) // [d1] пока жив
Чтобы удобно очищать массив от устаревших ссылок, добавьте метод:
extension Array where Element: WeakRef<AnyObject> {
mutating func compact() {
self = self.filter { $0.value != nil }
}
}
Решение 2: Использование NSMutableArray с NSPointerFunctions
Если вам нужно взаимодействие с Objective-C или просто знакомый инструмент:
import Foundation
let weakArray = NSMutableArray()
let pointerFunctions = NSPointerFunctions.weakObjects()
let pointerArray = NSPointerArray(options: pointerFunctions)
// Добавление объектов
let obj = NSObject()
let ptr = Unmanaged.passUnretained(obj).toOpaque()
pointerArray.addPointer(ptr)
// Чтение
pointerArray.allObjects // вернёт только живые объекты
Но этот способ менее типобезопасен и требует ручного управления указателями.
Решение 3: NSHashTable (для уникальности)
Если вам нужен набор слабых ссылок без дубликатов:
let weakSet = NSHashTable<AnyObject>(options: .weakMemory)
weakSet.add(d1)
weakSet.allObjects // [d1] или пустой после освобождения
Рекомендации
- Для чистого Swift используйте
WeakRef— это идиоматично и безопасно. - Для коллекций с уникальными элементами —
NSHashTable. - Для больших массивов периодически вызывайте
compact()для удаления nil-элементов. - Не забывайте, что слабые ссылки могут стать nil в любой момент, поэтому всегда проверяйте через optional binding.
Пример полного класса с автоочисткой:
class WeakDelegates<T: AnyObject> {
private var storage = [WeakRef<T>]()
func add(_ delegate: T) {
storage.append(WeakRef(delegate))
}
var alive: [T] {
storage = storage.filter { $0.value != nil }
return storage.compactMap { $0.value }
}
}
<<Альтернативный подход: использование замыканий вместо массива делегатов
Иногда проблему слабых ссылок можно решить архитектурно — заменить массив делегатов на массив замыканий. Это особенно удобно, если делегаты выполняют простые действия.
class SomeClass {
private var observers = [() -> Void]()
func addObserver(owner: AnyObject, closure: @escaping () -> Void) {
// Используем слабую ссылку на owner внутри замыкания
observers.append { [weak owner] in
guard owner != nil else { return } // если owner умер, не вызываем
closure()
}
}
func notify() {
// Очищаем мёртвые замыкания перед вызовом
observers = observers.filter { $0() != nil } // хитрый трюк, но лучше так:
observers = observers.filter { /* проверка */ true }
observers.forEach { $0() }
}
}
Этот подход не требует создания дополнительных структур и автоматически решает проблему утечек, так как замыкание захватывает owner слабо. Однако он менее гибок, если делегат должен реализовывать несколько методов.
Для простых callback'ов (например, обновление UI) это отличная альтернатива классическому паттерну делегата. Минус — сложность отладки и невозможность проверить тип делегата.
Ответов пока нет.
Войдите чтобы оставить ответ