Как мне объявить массив слабых ссылок в Swift?

179

Я хотел бы хранить массив слабых ссылок в Swift. Сам массив не должен быть слабой ссылкой - его элементы должны быть. Я думаю, что Какао NSPointerArrayпредлагает нетипичную версию этого.

Билл
источник
1
Как насчет создания объекта-контейнера, который слабо ссылается на другой объект, а затем создания массива из них? (Если вы не получите лучший ответ)
nielsbot
1
почему вы не используете NSPointerArray?
Бастиан
@nielsbot Это старое решение obj-c :) Чтобы сделать его Swifty, это должен быть универсальный объект! :) Однако реальная проблема состоит в том, как удалить объекты из массива, когда указанный объект освобожден.
Sulthan
2
Хорошо, я бы предпочел что-то с параметризованными типами. Я думаю, я мог бы сделать параметризованную оболочку вокруг NSPointerArray, но хотел посмотреть, есть ли какие-либо альтернативы.
Билл
6
Так же, как и другой вариант, существует NSHashTable. Это в основном NSSet, который позволяет вам указать, как он должен ссылаться на содержащиеся в нем объекты.
Мик МакКаллум

Ответы:

154

Создайте универсальную оболочку как:

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

Добавьте экземпляры этого класса в ваш массив.

class Stuff {}
var weakly : [Weak<Stuff>] = [Weak(value: Stuff()), Weak(value: Stuff())]

При определении Weakвы можете использовать либо structили class.

Также, чтобы помочь с получением содержимого массива, вы можете сделать что-то вроде:

extension Array where Element:Weak<AnyObject> {
  mutating func reap () {
    self = self.filter { nil != $0.value }
  }
}

Использование AnyObjectвыше должно быть заменено на T- но я не думаю, что текущий язык Swift допускает расширение, определенное как таковое.

GoZoner
источник
11
Как удалить объекты-оболочки из массива, когда их значение освобождено?
Sulthan
9
Да, это сломало компилятор.
GoZoner
5
Пожалуйста, оставьте свой код проблемы в новом вопросе; нет причин звонить мне в ответ, когда это может быть ваш код!
GoZoner
2
@EdGamble Предоставленный код работает как есть, но не работает, если вы заменяете класс Stuffпротоколом; см. этот связанный вопрос
Тео
2
Структура будет лучше, так как она будет храниться в стеке вместо необходимости извлечения кучи.
KPM
60

Вы можете использовать NSHashTable со слабой таблицей. NSHashTable<ObjectType>.weakObjectsHashTable()

Для Swift 3: NSHashTable<ObjectType>.weakObjects()

Ссылка на класс NSHashTable

Доступный в OS X v10.5 и позже.

Доступный в iOS 6.0 и позже.

Thierry
источник
Лучший ответ и не трать время на фантики!
Рамис
1
Это умно, но, как и ответ GoZoner, это не работает с типами, которые есть, Anyно не с AnyObjectтакими, как протоколы.
Аарон Брагер,
@SteveWilford Но протокол может быть реализован классом, который сделает его ссылочным типом
Аарон Брагер,
4
протокол может расширять класс, а затем вы можете использовать его как слабый (например, протокол MyProtocol: класс)
Ясмин Тёмкин
1
Я получаю ошибку компилятора с MyProtocol: classи NSHashTable<MyProtocol>.weakObjects(). «NSHashTable» требует, чтобы «MyProtocol» был типом класса.
Грег
14

Уже поздно для вечеринки, но попробуй мою. Я реализовал как набор, а не массив.

WeakObjectSet

class WeakObject<T: AnyObject>: Equatable, Hashable {
    weak var object: T?
    init(object: T) {
        self.object = object
    }

    var hashValue: Int {
        if let object = self.object { return unsafeAddressOf(object).hashValue }
        else { return 0 }
    }
}

func == <T> (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
    return lhs.object === rhs.object
}


class WeakObjectSet<T: AnyObject> {
    var objects: Set<WeakObject<T>>

    init() {
        self.objects = Set<WeakObject<T>>([])
    }

    init(objects: [T]) {
        self.objects = Set<WeakObject<T>>(objects.map { WeakObject(object: $0) })
    }

    var allObjects: [T] {
        return objects.flatMap { $0.object }
    }

    func contains(object: T) -> Bool {
        return self.objects.contains(WeakObject(object: object))
    }

    func addObject(object: T) {
        self.objects.unionInPlace([WeakObject(object: object)])
    }

    func addObjects(objects: [T]) {
        self.objects.unionInPlace(objects.map { WeakObject(object: $0) })
    }
}

использование

var alice: NSString? = "Alice"
var bob: NSString? = "Bob"
var cathline: NSString? = "Cathline"

var persons = WeakObjectSet<NSString>()
persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObjects([alice!, cathline!])
print(persons.allObjects) // [Alice, Cathline, Bob]

alice = nil
print(persons.allObjects) // [Cathline, Bob]

bob = nil
print(persons.allObjects) // [Cathline]

Помните, что WeakObjectSet не будет принимать тип String, но NSString. Потому что тип String не является AnyType. Моя быстрая версия есть Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

Код можно получить из Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** ДОБАВЛЕНО В НОЯРЕ 2017

Я обновил код до Swift 4

// Swift 4, Xcode Version 9.1 (9B55)

class WeakObject<T: AnyObject>: Equatable, Hashable {
    weak var object: T?
    init(object: T) {
        self.object = object
    }

    var hashValue: Int {
        if var object = object { return UnsafeMutablePointer<T>(&object).hashValue }
        return 0
    }

    static func == (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
        return lhs.object === rhs.object
    }
}

class WeakObjectSet<T: AnyObject> {
    var objects: Set<WeakObject<T>>

    init() {
        self.objects = Set<WeakObject<T>>([])
    }

    init(objects: [T]) {
        self.objects = Set<WeakObject<T>>(objects.map { WeakObject(object: $0) })
    }

    var allObjects: [T] {
        return objects.flatMap { $0.object }
    }

    func contains(_ object: T) -> Bool {
        return self.objects.contains(WeakObject(object: object))
    }

    func addObject(_ object: T) {
        self.objects.formUnion([WeakObject(object: object)])
    }

    func addObjects(_ objects: [T]) {
        self.objects.formUnion(objects.map { WeakObject(object: $0) })
    }
}

Как упомянул gokeji, я понял, что NSString не будет освобожден в зависимости от используемого кода. Я почесал голову и написал класс MyString следующим образом.

// typealias MyString = NSString
class MyString: CustomStringConvertible {
    var string: String
    init(string: String) {
        self.string = string
    }
    deinit {
        print("relasing: \(string)")
    }
    var description: String {
        return self.string
    }
}

Затем замените NSStringна MyStringвот так. Тогда странно сказать, что это работает.

var alice: MyString? = MyString(string: "Alice")
var bob: MyString? = MyString(string: "Bob")
var cathline: MyString? = MyString(string: "Cathline")

var persons = WeakObjectSet<MyString>()

persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObjects([alice!, cathline!])
print(persons.allObjects) // [Alice, Cathline, Bob]

alice = nil
print(persons.allObjects) // [Cathline, Bob]

bob = nil
print(persons.allObjects) // [Cathline]

Тогда я обнаружил, что странная страница может быть связана с этой проблемой.

Слабая ссылка сохраняет освобожденную строку NSString (только XC9 + iOS Sim)

https://bugs.swift.org/browse/SR-5511

Там говорится, что проблема в том, RESOLVEDно мне интересно, связано ли это еще с этой проблемой. В любом случае, различия в поведении между MyString и NSString выходят за рамки этого контекста, но я был бы признателен, если бы кто-то решил эту проблему.

Каз Йошикава
источник
Я принял это решение для своего проекта. Прекрасная работа! Только одно предположение, это решение, похоже, не удаляет nilзначения из внутреннего Set. Поэтому я добавил reap()функцию, упомянутую в верхнем ответе, и проверял, чтобы она вызывалась при reap()каждом WeakObjectSetобращении к ней.
Гокеджи
Хм, подождите, по какой-то причине это не работает в Swift 4 / iOS 11. Похоже, что слабая ссылка не освобождается сразу, когда значение становится nilбольше
gokeji
1
Я обновил код до Swift4, см. Вторую половину ответа. Мне кажется, NSString имеет некоторые проблемы освобождения, но он все равно должен работать на ваших пользовательских объектах класса.
Каз Йошикава
Большое спасибо за просмотр @KazYoshikawa и обновление ответа! Позже я также понял, что пользовательский класс работает, а NSStringнет.
gokeji
2
Я понял, что указатель, который возвращается, UnsafeMutablePointer<T>(&object)может меняться случайным образом (то же самое с withUnsafePointer). Теперь я использую версию, поддерживаемую NSHashTable: gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d .
simonseyer
12

Это не мое решение. Я нашел это на форумах разработчиков Apple .

У @GoZoner хороший ответ, но он ломает компилятор Swift.

Вот версия контейнера слабых объектов, которая не приводит к сбою текущего выпущенного компилятора.

struct WeakContainer<T where T: AnyObject> {
    weak var _value : T?

    init (value: T) {
        _value = value
    }

    func get() -> T? {
        return _value
    }
}

Затем вы можете создать массив этих контейнеров:

let myArray: Array<WeakContainer<MyClass>> = [myObject1, myObject2]
rjkaplan
источник
1
странно, но больше не работает со структурами. Говорит EXC_BAD_ACCESSдля меня. С классом работает просто отлично
mente
6
Структуры являются типами значений, они не должны работать с ними. Тот факт, что он завис во время выполнения, а не во время компиляции, является ошибкой компилятора.
Дэвид Гудин
10

Вы можете сделать это, создав объект-обертку для хранения слабого указателя.

struct WeakThing<T: AnyObject> {
  weak var value: T?
  init (value: T) {
    self.value = value
  }
}

И затем, используя их в массиве

var weakThings = WeakThing<Foo>[]()
Джошуа Вайнберг
источник
Должно быть, classчтобы использовать weakпеременные
Билл
3
Говорит кто? Код выше работает хорошо для меня. Единственное требование состоит в том, что объект, который становится слабым, должен быть классом, а не объектом, содержащим слабую ссылку
Джошуа Вайнберг
Сожалею. Я мог бы поклясться, что только что получил сообщение компилятора, которое говорит: «Не могу использовать слабые переменные в структурах». Вы правы - это компилируется.
Билл
5
@JoshuaWeinberg, что если Фу - протокол?
onmyway133
@ onmyway133 AFAIK, если протокол объявлен реализованным только классами, он будет работать. protocol Protocol : class { ... }
olejnjak
8

Как насчет функционального стиля оболочки?

class Class1 {}

func captureWeakly<T> (_ target:T) -> (() -> T?) where T: AnyObject {
    return { [weak target] in
        return target
    }
}

let obj1 = Class1()
let obj2 = Class1()
let obj3 = Class1()
let captured1 = captureWeakly(obj1)
let captured2 = captureWeakly(obj2)
let captured3 = captureWeakly(obj3)

Просто позвоните, верните закрытие, чтобы проверить, что цель еще жива.

let isAlive = captured1() != nil
let theValue = captured1()!

И вы можете хранить эти замыкания в массиве.

let array1 = Array<() -> (Class1?)>([captured1, captured2, captured3])

И вы можете получить слабо захваченные значения, сопоставляя вызовы замыканий.

let values = Array(array1.map({ $0() }))

На самом деле, вам не нужна функция для закрытия. Просто захватите объект напрямую.

let captured3 = { [weak obj3] in return obj3 }
eonil
источник
3
Вопрос в том, как создать массив (или, скажем, Set) слабых объектов.
Дэвид Х
С помощью этого решения вы даже можете создать массив с несколькими значениями, такими как var array: [(x: Int, y: () -> T?)]. Именно то, что я искал.
jboi
1
@DavidH Я обновил свой ответ, чтобы ответить на вопрос. Надеюсь, это поможет.
Эонил
Мне понравился этот подход, и я думаю, что он очень умный. Я сделал реализацию класса, используя эту стратегию. Спасибо!
Эль Равасио
Не слишком уверен в этом let values = Array(array1.map({ $0() })) part. Поскольку это больше не массив закрытий со слабыми ссылками, значения будут сохраняться до тех пор, пока этот массив не будет освобожден. Если я прав, то важно отметить, что вы никогда не должны сохранять этот массив, self.items = Array(array1.map({ $0() }))как это соответствует цели.
Матик Облак
7

У меня была такая же идея создать слабый контейнер с генериками.
В результате я создал обертку для NSHashTable:

class WeakSet<ObjectType>: SequenceType {

    var count: Int {
        return weakStorage.count
    }

    private let weakStorage = NSHashTable.weakObjectsHashTable()

    func addObject(object: ObjectType) {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        weakStorage.addObject(object as? AnyObject)
    }

    func removeObject(object: ObjectType) {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        weakStorage.removeObject(object as? AnyObject)
    }

    func removeAllObjects() {
        weakStorage.removeAllObjects()
    }

    func containsObject(object: ObjectType) -> Bool {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        return weakStorage.containsObject(object as? AnyObject)
    }

    func generate() -> AnyGenerator<ObjectType> {
        let enumerator = weakStorage.objectEnumerator()
        return anyGenerator {
            return enumerator.nextObject() as! ObjectType?
        }
    }
}

Использование:

protocol MyDelegate : AnyObject {
    func doWork()
}

class MyClass: AnyObject, MyDelegate {
    fun doWork() {
        // Do delegated work.
    }
}

var delegates = WeakSet<MyDelegate>()
delegates.addObject(MyClass())

for delegate in delegates {
    delegate.doWork()
}

Это не лучшее решение, потому что WeakSetможет быть инициализировано с любым типом, и если этот тип не соответствует AnyObjectпротоколу, приложение будет аварийно завершено с подробной причиной. Но я не вижу лучшего решения прямо сейчас.

Первоначальное решение было определить WeakSetтаким образом:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

Но в этом случае WeakSetнельзя инициализировать протоколом:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

В настоящее время приведенный выше код не может быть скомпилирован (Swift 2.1, Xcode 7.1).
Вот почему я упал в соответствии сAnyObject и добавил дополнительных охранников с fatalError()утверждениями.

Влад Папко
источник
Просто используйте для объекта в hashtable.allObjects
malhal
6

подробности

  • Swift 5.1, Xcode 11.3.1

Решение

struct WeakObject<Object: AnyObject> { weak var object: Object? }

Опция 1

@propertyWrapper
struct WeakElements<Collect, Element> where Collect: RangeReplaceableCollection, Collect.Element == Optional<Element>, Element: AnyObject {
    private var weakObjects = [WeakObject<Element>]()

    init(wrappedValue value: Collect) { save(collection: value) }

    private mutating func save(collection: Collect) {
        weakObjects = collection.map { WeakObject(object: $0) }
    }

    var wrappedValue: Collect {
        get { Collect(weakObjects.map { $0.object }) }
        set (newValues) { save(collection: newValues) }
    }
}

Вариант 1 использования

class Class1 { // or struct
    @WeakElements var weakObjectsArray = [UIView?]() // Use like regular array. With any objects

    func test() {
        weakObjectsArray.append(UIView())
        weakObjectsArray.forEach { print($0) }
    }
}

Вариант 2

struct WeakObjectsArray<Object> where Object: AnyObject {
    private var weakObjects = [WeakObject<Object>]()
}

extension WeakObjectsArray {
    typealias SubSequence = WeakObjectsArray<Object>
    typealias Element = Optional<Object>
    typealias Index = Int
    var startIndex: Index { weakObjects.startIndex }
    var endIndex: Index { weakObjects.endIndex }
    func index(after i: Index) -> Index { weakObjects.index(after: i) }
    subscript(position: Index) -> Element {
        get { weakObjects[position].object }
        set (newValue) { weakObjects[position] = WeakObject(object: newValue) }
    }
    var count: Int { return weakObjects.count }
    var isEmpty: Bool { return weakObjects.isEmpty }
}

extension WeakObjectsArray: RangeReplaceableCollection {
    mutating func replaceSubrange<C : Collection>( _ subrange: Range<Index>, with newElements: C) where Element == C.Element {
        weakObjects.replaceSubrange(subrange, with: newElements.map { WeakObject(object: $0) })
    }
}

Вариант 2 использования

class Class2 { // or struct
    var weakObjectsArray = WeakObjectsArray<UIView>() // Use like regular array. With any objects

    func test() {
        weakObjectsArray.append(UIView())
        weakObjectsArray.forEach { print($0) }
    }
}

Полный образец

не забудьте вставить код решения

import UIKit

class ViewController: UIViewController {

    @WeakElements var weakObjectsArray = [UIView?]()
    //var weakObjectsArray = WeakObjectsArray<UIView>()

    override func viewDidLoad() {
        super.viewDidLoad()
        addSubviews()
    }

    private func printArray(title: String) {
        DispatchQueue.main.async {
            print("=============================\n\(title)\ncount: \(self.weakObjectsArray.count)")
            self.weakObjectsArray.enumerated().forEach { print("\($0) \(String(describing: $1))") }
        }
    }
}

extension ViewController {

    private func createRandomRectangleAndAdd(to parentView: UIView) -> UIView {
        let view = UIView(frame: CGRect(x: Int.random(in: 0...200),
                                        y: Int.random(in: 60...200),
                                        width: Int.random(in: 0...200),
                                        height: Int.random(in: 0...200)))
        let color = UIColor(red: CGFloat.random(in: 0...255)/255,
                            green: CGFloat.random(in: 0...255)/255,
                            blue: CGFloat.random(in: 0...255)/255,
                            alpha: 1)
        view.backgroundColor = color
        parentView.addSubview(view)
        return view
    }

    private func addSubviews() {
        (0...1).forEach { _ in addView() }
        addButtons()
    }

    private func createButton(title: String, frame: CGRect, action: Selector) -> UIButton {
        let button = UIButton(frame: frame)
        button.setTitle(title, for: .normal)
        button.addTarget(self, action: action, for: .touchUpInside)
        button.setTitleColor(.blue, for: .normal)
        return button
    }

    private func addButtons() {
        view.addSubview(createButton(title: "Add",
                                     frame: CGRect(x: 10, y: 20, width: 40, height: 40),
                                     action: #selector(addView)))

        view.addSubview(createButton(title: "Delete",
                                     frame: CGRect(x: 60, y: 20, width: 60, height: 40),
                                     action: #selector(deleteView)))

        view.addSubview(createButton(title: "Remove nils",
                                     frame: CGRect(x: 120, y: 20, width: 100, height: 40),
                                     action: #selector(removeNils)))
    }

    @objc func deleteView() {
        view.subviews.first { view -> Bool in return !(view is UIButton) }?
            .removeFromSuperview()

        printArray(title: "First view deleted")
    }

    @objc func addView() {
        weakObjectsArray.append(createRandomRectangleAndAdd(to: view))
        printArray(title: "View addded")
    }

    @objc func removeNils() {
        weakObjectsArray = weakObjectsArray.filter { $0 != nil }
        printArray(title: "Remove all nil elements in weakArray")
    }
}
Василий Боднарчук
источник
Моя проблема с обоими вариантами (и многими другими) состоит в том, что эти типы массивов не могут использоваться с протоколами. Например, это не скомпилирует:protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
Matic Oblak
@MaticOblak как насчет использования дженериков? protocol TP: class { } class TC<TYPE> where TYPE: TP { var a = WeakObjectsArray<TYPE>() // Use like regular array. With any objects var weakObjectsArray = [TYPE?]() }
Василий Боднарчук
Идея состоит в том, что этот массив может содержать объекты разных типов, которые реализуют один и тот же протокол класса. Используя универсальный, вы фиксируете его на один тип. Например, представьте, что у вас есть синглтон, содержащий такой массив как delegates. Тогда у вас будет некоторое количество контроллеров представления, которые хотели бы использовать эту функцию. Вы ожидаете позвонить MyManager.delegates.append(self). Но если MyManagerпривязан к какому-то универсальному типу, тогда это не очень удобно.
Матик Облак
@MaticOblak хорошо. Попробуйте это: protocol TP: class { } class MyManager { typealias Delegate = AnyObject & TP static var delegates = [Delegate?]() } class A: TP { } class B: TP { } //MyManager.delegates.append(A()) //MyManager.delegates.append(B())
Василий Боднарчук
Вы потеряли общую часть массива, что немного важно :) У меня такое ощущение, что это просто невозможно. Ограничение Свифта на данный момент ...
Матик Облак
4

Существующий пример WeakContainer полезен, но на самом деле он не помогает использовать слабые ссылки в существующих быстрых контейнерах, таких как списки и словари.

Если вы хотите использовать методы List, такие как contains, то WeakContainer должен будет реализовать Equatable. Поэтому я добавил код, чтобы позволить WeakContainer быть уравновешенным.

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

Я также переименовал его в WeakObject, чтобы подчеркнуть, что это только для типов классов, и дифференцировать его от примеров WeakContainer:

struct WeakObject<TYPE where TYPE:AnyObject> : Equatable, Hashable
{
    weak var _value : TYPE?
    let _originalHashValue : Int

    init (value: TYPE)
    {
        _value = value

        // We keep around the original hash value so that we can return it to represent this
        // object even if the value became Nil out from under us because the object went away.
        _originalHashValue = ObjectIdentifier(value).hashValue
    }

    var value : TYPE?
    {
        return _value
    }

    var hashValue: Int
    {
        return _originalHashValue
    }
}

func ==<T>(lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool
{
    if lhs.value == nil  &&  rhs.value == nil {
        return true
    }
    else if lhs.value == nil  ||  rhs.value == nil {
        return false
    }

    // If the objects are the same, then we are good to go
    return lhs.value! === rhs.value!
}

Это позволяет вам делать некоторые интересные вещи, такие как использование словаря слабых ссылок:

private var m_observerDict : Dictionary<WeakObject<AnyObject>,FLObservationBlock> = Dictionary()

func addObserver( observer:AnyObject, block:FLObservationBlock )
{
    let weakObserver = WeakObject(value:observer)
    m_observerDict[weakObserver] = block
}


func removeObserver( observer:AnyObject )
{
    let weakObserver = WeakObject(value:observer)
    m_observerDict.removeValueForKey(weakObserver)
}
Тод Каннингем
источник
3

Вот как сделать @ большой ответ GoZoner в соответствие Hashable, так что он может быть проиндексирован в объекты Container , как: Set, Dictionary, Arrayи т.д.

private class Weak<T: AnyObject>: Hashable {
    weak var value : T!
    init (value: T) {
       self.value = value
    }

    var hashValue : Int {
       // ObjectIdentifier creates a unique hashvalue for objects.
       return ObjectIdentifier(self.value).hashValue
    }
}

// Need to override so we can conform to Equitable.
private func == <T>(lhs: Weak<T>, rhs: Weak<T>) -> Bool {
    return lhs.hashValue == rhs.hashValue
}
Sakiboy
источник
3

Поскольку NSPointerArrayбольшая часть этого уже обрабатывается автоматически, я решил эту проблему, создав безопасную для типов оболочку, которая позволяет избежать большого количества шаблонов в других ответах:

class WeakArray<T: AnyObject> {
    private let pointers = NSPointerArray.weakObjects()

    init (_ elements: T...) {
        elements.forEach{self.pointers.addPointer(Unmanaged.passUnretained($0).toOpaque())}
    }

    func get (_ index: Int) -> T? {
        if index < self.pointers.count, let pointer = self.pointers.pointer(at: 0) {
            return Unmanaged<T>.fromOpaque(pointer).takeUnretainedValue()
        } else {
            return nil
        }
    }
    func append (_ element: T) {
        self.pointers.addPointer(Unmanaged.passUnretained(element).toOpaque())
    }
    func forEach (_ callback: (T) -> ()) {
        for i in 0..<self.pointers.count {
            if let element = self.get(i) {
                callback(element)
            }
        }
    }
    // implement other functionality as needed
}

Пример использования:

class Foo {}
var foo: Foo? = Foo()
let array = WeakArray(foo!)
print(array.get(0)) // Optional(Foo)
foo = nil
DispatchQueue.main.async{print(array.get(0))} // nil

Это больше работы, но использование в остальной части кода намного чище IMO. Если вы хотите сделать его более похожим на массив, вы можете даже реализовать подписку, сделать это и SequenceTypeт. Д. (Но мой проект нуждается только в этом, appendи forEachпоэтому у меня нет точного кода под рукой).

Джон Монтгомери
источник
2

Еще одно решение той же проблемы ... основное внимание уделяется хранению слабой ссылки на объект, но также позволяет хранить структуру.

[Я не уверен, насколько это полезно, но потребовалось время, чтобы понять синтаксис]

class WeakWrapper : Equatable {
    var valueAny : Any?
    weak var value : AnyObject?

    init(value: Any) {
        if let valueObj = value as? AnyObject {
            self.value = valueObj
        } else {
            self.valueAny = value
        }
    }

    func recall() -> Any? {
        if let value = value {
            return value
        } else if let value = valueAny {
            return value
        }
        return nil
    }
}


func ==(lhs: WeakWrapper, rhs: WeakWrapper) -> Bool {
    return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}



class Stuff {}
var weakArray : [WeakWrapper] = [WeakWrapper(value: Stuff()), WeakWrapper(value: CGRectZero)]

extension Array where Element : WeakWrapper  {

    mutating func removeObject(object: Element) {
        if let index = self.indexOf(object) {
            self.removeAtIndex(index)
        }
    }

    mutating func compress() {
        for obj in self {
            if obj.recall() == nil {
                self.removeObject(obj)
            }
        }
    }


}

weakArray[0].recall()
weakArray[1].recall() == nil
weakArray.compress()
weakArray.count
Дэн Розенстарк
источник
1

Другие ответы охватили общий вид. Думаю, я бы поделился простым кодом, охватывающим этот nilугол.

Я хотел статический массив (иногда читаемый) из всех, Labelкоторые в настоящее время существуют в приложении, но не хотел видеть nil, где были старые.

Ничего особенного, это мой код ...

public struct WeakLabel {
    public weak var label : Label?
    public init(_ label: Label?) {
        self.label = label
    }
}

public class Label : UILabel {
    static var _allLabels = [WeakLabel]()
    public static var allLabels:[WeakLabel] {
        get {
            _allLabels = _allLabels.filter{$0.label != nil}
            return _allLabels.filter{$0.label != nil}.map{$0.label!}
        }
    }
    public required init?(coder: NSCoder) {
        super.init(coder: coder)
        Label._allLabels.append(WeakLabel(self))
    }
    public override init(frame: CGRect) {
        super.init(frame: frame)
        Label._allLabels.append(WeakLabel(self))
    }
}
Wils
источник
Как насчет использования flatMapвместо filter& map?
Лукас Кубанек
0

Я основал это на работе @Eonil, так как мне понравилась стратегия слабого связывания замыкания, но я не хотел использовать оператор функции для переменной, так как она была чрезвычайно противоречивой.

Вместо этого я сделал следующее:

class Weak<T> where T: AnyObject {
    fileprivate var storedWeakReference: ()->T? = { return nil }

    var value: T? {
        get {
            return storedWeakReference()
        }
    }

    init(_ object: T) {
        self.storedWeakReference = storeWeakReference(object)
    }

    fileprivate func storeWeakReference<T> (_ target:T) -> ()->T? where T: AnyObject {
        return { [weak target] in
            return target
        }
    }
}

Таким образом, вы можете сделать что-то вроде:

var a: UIViewController? = UIViewController()
let b = Weak(a)
print(a) //prints Optional(<UIViewController: 0xSomeAddress>)
print(b.value) //prints Optional(<UIViewController: 0xSomeAddress>)
a = nil
print(a) //prints nil
print(b.value) //prints nil
Эль Равасио
источник
0

Это мое решение:

  • Очистите массив при освобождении , потому что WeakObjectSet хранит и не выходит из WeakObject
  • Устраните фатальную ошибку при обнаружении дубликата элемента в наборе

-

// MARK: - WeakObjectSet 

public class WeakObject<T: AnyObject>: Equatable, Hashable {

    // MARK: Public propreties

    public weak var object: T?
    public var hashValue: Int {
        return self.identifier.hashValue
    }

    // MARK: Private propreties

    private let identifier: ObjectIdentifier

    // MARK: Initializer

    public init(object: T) {
        self.identifier = ObjectIdentifier(object)
        self.object = object
    }

    public static func == (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
        return lhs.identifier == rhs.identifier
    }
}

// MARK: - WeakObjectSet

public class WeakObjectSet<T: AnyObject> {

    // MARK: Public propreties

    public var allObjects: [T] {
        return allSetObjects.compactMap { $0.object }
    }

    // MARK: Private propreties

    private var objects: Set<WeakObject<T>>
    private var allSetObjects: Set<WeakObject<T>> {
        get {
            objects = self.objects.filter { $0.object != nil }
            return objects
        }
        set {
            objects.formUnion(newValue.filter { $0.object != nil })
        }
    }

    // MARK: Initializer

    public init() {
        self.objects = Set<WeakObject<T>>([])
    }

    public init(objects: [T]) {
        self.objects = Set<WeakObject<T>>(objects.map { WeakObject(object: $0) })
    }

    // MARK: Public function

    public func contains(_ object: T) -> Bool {
        return self.allSetObjects.contains(WeakObject(object: object))
    }

    public func addObject(_ object: T) {
        self.allSetObjects.insert(WeakObject(object: object))
    }

    public func addObjects(_ objects: [T]) {
        objects.forEach { self.allSetObjects.insert(WeakObject(object: $0)) }
    }
}
YannSteph
источник
0

Это безопасная коллекция типов, которая содержит контейнеры со слабыми объектами. Он также автоматически удаляет ноль контейнеров / упаковщиков при доступе.

Пример:

protocol SomeDelegate: class {
    func doSomething()
}

class SomeViewController: UIViewController {
    var delegates: WeakCollection<SomeDelegate> = []

    func someFunction(delegate: SomeDelegate) {
        delegates.append(delegate)
    }

    func runDelegates() {
        delegates.forEach { $0.doSomething() }
    }
}

Пользовательская коллекция https://gist.github.com/djk12587/46d85017fb3fad6946046925f36cefdc

import Foundation

/**
 Creates an array of weak reference objects.
 - Important:
    Because this is an array of weak objects, the objects in the array can be removed at any time.
    The collection itself will handle removing nil objects (garbage collection) via the private function cleanUpNilContainers()
 */

class WeakCollection<T>: RangeReplaceableCollection, ExpressibleByArrayLiteral {
    typealias Index = Int
    typealias Element = T
    typealias Iterator = IndexingIterator<[Element]>

    private var weakContainers: [WeakReferenceContainer]

    required convenience init(arrayLiteral: Element...) {
        self.init()
        self.weakContainers = WeakCollection.createWeakContainers(from: arrayLiteral)
    }

    required init() {
        weakContainers = []
    }

    required init<S>(_ elements: S) where S: Sequence, WeakCollection.Element == S.Element {
        self.weakContainers = WeakCollection.createWeakContainers(from: elements)
    }

    static private func createWeakContainers<S>(from weakCollection: S) -> [WeakReferenceContainer] where S: Sequence,
        WeakCollection.Element == S.Element {
            return weakCollection.compactMap { WeakReferenceContainer(value: $0 as AnyObject) }
    }

    func append<S>(contentsOf newElements: S) where S: Sequence, WeakCollection.Element == S.Element {
        self.weakContainers.append(contentsOf: WeakCollection.createWeakContainers(from: newElements))
    }

    var startIndex: Index {
        return references.startIndex
    }

    var endIndex: Index {
        return references.endIndex
    }

    func replaceSubrange<C, R>(_ subrange: R, with newElements: C) where
        C: Collection, R: RangeExpression, WeakCollection.Element == C.Element, WeakCollection.Index == R.Bound {
            weakContainers.replaceSubrange(subrange, with: WeakCollection.createWeakContainers(from: newElements))
    }

    func index(after i: Int) -> Int {
        return references.index(after: i)
    }

    func makeIterator() -> IndexingIterator<[Element]> {
        return references.makeIterator()
    }

    subscript(index: Int) -> Element {
        get {
            return references[index]
        }
        set {
            weakContainers[index] = WeakReferenceContainer(value: newValue as AnyObject)
        }
    }
}

extension WeakCollection {
    private class WeakReferenceContainer {
        private(set) weak var value: AnyObject?

        init(value: AnyObject?) {
            self.value = value
        }
    }

    private func cleanUpNilContainers() {
        weakContainers = weakContainers.compactMap { $0.value == nil ? nil : $0 }
    }

    private var references: [Element] {
        cleanUpNilContainers()
        return weakContainers.compactMap { $0.value as? T }
    }
}
Дэн
источник
0

Как насчет функционального подхода ?

let observers = [() -> Observer?]()

observers.append({ [weak anObserver] in return anObserver })

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

frouo
источник