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

Как реализуешь коллекцию которая хранит Weak ссылки на объекты?

3.0 Senior🔥 142 комментариев
#Управление памятью

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

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

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

Реализация коллекции со слабыми ссылками в iOS

Для реализации коллекции, хранящей weak ссылки на объекты в iOS/Swift, необходимо учитывать особенности управления памятью и то, что стандартные коллекции (Array, Set, Dictionary) хранят strong references. Я рассмотрю несколько подходов, начиная с наиболее распространённого.

Ключевая концепция: NSHashTable

Основной способ — использование NSHashTable из Foundation, который напрямую поддерживает weak хранение:

import Foundation

class WeakObjectCollection<T: AnyObject> {
    private weak var table = NSHashTable<T>.weakObjects()
    
    var allObjects: [T] {
        return table.allObjects.compactMap { $0 }
    }
    
    func add(_ object: T) {
        table.add(object)
    }
    
    func remove(_ object: T) {
        table.remove(object)
    }
    
    func contains(_ object: T) -> Bool {
        return table.contains(object)
    }
    
    func removeAll() {
        table.removeAllObjects()
    }
}

// Пример использования
class MyClass {
    let name: String
    init(name: String) { self.name = name }
}

let collection = WeakObjectCollection<MyClass>()
var instance: MyClass? = MyClass(name: "Test")

collection.add(instance!)
print(collection.allObjects.count) // 1

instance = nil // Объект должен быть освобождён
print(collection.allObjects.count) // 0 (автоматически очищено)

Альтернативная реализация через массив weak ссылок

Можно создать обёртку для массива, используя weak свойства внутри структуры-контейнера:

class WeakBox<T: AnyObject> {
    weak var value: T?
    
    init(_ value: T) {
        self.value = value
    }
}

struct WeakArray<T: AnyObject> {
    private var boxes: [WeakBox<T>] = []
    
    var allObjects: [T] {
        // Автоматическая очистка nil значений при обращении
        boxes = boxes.filter { $0.value != nil }
        return boxes.compactMap { $0.value }
    }
    
    mutating func append(_ object: T) {
        boxes.append(WeakBox(object))
    }
    
    mutating func remove(_ object: T) {
        boxes.removeAll { $0.value === object || $0.value == nil }
    }
    
    mutating func compact() {
        boxes = boxes.filter { $0.value != nil }
    }
}

Реализация weak словаря через NSMapTable

Для пар "ключ-значение" следует использовать NSMapTable:

class WeakDictionary<Key: AnyObject & Hashable, Value: AnyObject> {
    private let mapTable = NSMapTable<Key, Value>(
        keyOptions: [.strongMemory],
        valueOptions: [.weakMemory]
    )
    
    var allKeys: [Key] {
        return mapTable.keyEnumerator().allObjects as? [Key] ?? []
    }
    
    var allValues: [Value] {
        return mapTable.objectEnumerator()?.allObjects.compactMap { $0 as? Value } ?? []
    }
    
    subscript(key: Key) -> Value? {
        get { return mapTable.object(forKey: key) }
        set {
            if let newValue = newValue {
                mapTable.setObject(newValue, forKey: key)
            } else {
                mapTable.removeObject(forKey: key)
            }
        }
    }
}

Важные аспекты реализации

  1. Автоматическая очистка: NSHashTable и NSMapTable автоматически удаляют nil ссылки, но не мгновенно. В кастомных реализациях нужно предусмотреть метод compact().

  2. Потокобезопасность: Коллекции по умолчанию не потокобезопасны. Для многопоточного доступа нужны механизмы синхронизации:

class ThreadSafeWeakCollection<T: AnyObject> {
    private var table = NSHashTable<T>.weakObjects()
    private let queue = DispatchQueue(label: "com.weakcollection.queue", 
                                     attributes: .concurrent)
    
    func add(_ object: T) {
        queue.async(flags: .barrier) {
            self.table.add(object)
        }
    }
    
    var allObjects: [T] {
        queue.sync {
            return self.table.allObjects.compactMap { $0 }
        }
    }
}
  1. Особенности Swift: В чистом Swift нет встроенных weak коллекций, поэтому используем Foundation (NSHashTable/NSMapTable) или создаём обёртки.

  2. Использование enums для более безопасного дизайна:

enum WeakCollection<T: AnyObject> {
    case hashTable(NSHashTable<T>)
    case customArray([WeakBox<T>])
    
    mutating func add(_ object: T) {
        switch self {
        case .hashTable(let table):
            table.add(object)
        case .customArray(var array):
            array.append(WeakBox(object))
            self = .customArray(array)
        }
    }
}

Практические рекомендации

  • Для большинства случаев используйте NSHashTable — это наиболее оптимизированное и надёжное решение
  • Реализуйте протокол Sequence/Collection для удобства использования в for-in циклах
  • Учитывайте производительность при частых операциях очистки
  • Тестируйте утечки памяти с помощью Instruments (Leaks tool)
  • Для observer-паттернов weak коллекции идеальны для хранения подписчиков

Выбор конкретной реализации зависит от требований: NSHashTable оптимален для производительности, кастомные решения дают больше гибкости, но требуют внимательной реализации очистки мёртвых ссылок.