¿Cómo declaro una serie de referencias débiles en Swift?

179

Me gustaría almacenar una serie de referencias débiles en Swift. La matriz en sí no debería ser una referencia débil, sus elementos deberían serlo. Creo que Cocoa NSPointerArrayofrece una versión no segura de esto.

Cuenta
fuente
1
¿Qué pasa con hacer un objeto contenedor que hace referencia débilmente a otro objeto, y luego hacer una matriz de esos? (Si no obtiene una mejor respuesta)
nielsbot
1
¿Por qué no usas un NSPointerArray?
Bastian
@nielsbot Esa es una vieja solución obj-c :) Para que sea Swifty, ¡debería ser un objeto genérico! :) Sin embargo, el verdadero problema es cómo eliminar objetos de la matriz cuando el objeto referenciado se desasigna.
Sulthan
2
Correcto, preferiría algo con tipos parametrizados. Supongo que podría hacer un contenedor parametrizado alrededor de NSPointerArray, pero quería ver si había alguna alternativa.
Bill
66
Como otra opción, existe NSHashTable. Básicamente es un NSSet que le permite especificar cómo debe hacer referencia a los objetos que contiene.
Mick MacCallum

Respuestas:

154

Cree un contenedor genérico como:

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

Agregue instancias de esta clase a su matriz.

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

Al definir Weakpuede usar cualquiera structo class.

Además, para ayudar a cosechar el contenido de la matriz, puede hacer algo en la línea de:

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

El uso de lo AnyObjectanterior debería reemplazarse con T, pero no creo que el lenguaje Swift actual permita una extensión definida como tal.

GoZoner
fuente
11
¿Cómo se eliminan los objetos de contenedor de la matriz cuando se desasigna su valor?
Sulthan
9
Sí, se bloqueó el compilador.
GoZoner
55
Publique su código de problema en una nueva pregunta; ¡No hay razón para decir mi respuesta cuando podría ser su código!
GoZoner
2
@EdGamble El código provisto funciona como es, pero falla si reemplaza la clase Stuffpor un protocolo; vea esta pregunta relacionada
Theo
2
Una estructura sería mejor, ya que se mantendría en la pila en lugar de necesitar una recuperación de almacenamiento dinámico.
KPM
60

Puede usar NSHashTable con weakObjectsHashTable. NSHashTable<ObjectType>.weakObjectsHashTable()

Para Swift 3: NSHashTable<ObjectType>.weakObjects()

Referencia de clase NSHashTable

Disponible en OS X v10.5 y posterior.

Disponible en iOS 6.0 y posterior.

Thierry
fuente
¡La mejor respuesta y no pierdas tiempo para envoltorios!
Ramis
1
Este es inteligente, pero al igual que la respuesta de GoZoner, esto no funciona con tipos que son Any, pero no AnyObject, como los protocolos.
Aaron Brager
@SteveWilford Pero una clase puede implementar un protocolo, lo que lo convertiría en un tipo de referencia
Aaron Brager
44
un protocolo puede extender la clase y luego puede usarlo como débil (por ejemplo, el protocolo MyProtocol: class)
Yasmin Tiomkin
1
Me sale un error de compilación con MyProtocol: classyNSHashTable<MyProtocol>.weakObjects() . "'NSHashTable' requiere que 'MyProtocol' sea un tipo de clase.
Greg
14

Es un poco tarde para la fiesta, pero prueba el mío. Implementé como un conjunto, no una matriz.

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) })
    }
}

Uso

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]

Tenga en cuenta que WeakObjectSet no tomará el tipo String sino NSString. Porque, el tipo de cadena no es un AnyType. Mi versión rápida es Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

El código puede ser tomado de Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** AGREGADO EN NOV.2017

Actualicé el código a 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) })
    }
}

Como mencionó Gokeji, descubrí que NSString no se desasignará en función del código en uso. Me rasqué la cabeza y escribí la clase MyString de la siguiente manera.

// 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
    }
}

Luego reemplace NSStringcon MyStringesto. Entonces extraño decir que funciona.

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]

Luego descubrí que una página extraña puede estar relacionada con este problema.

La referencia débil conserva NSString desasignado (solo XC9 + iOS Sim)

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

Dice que el problema es, RESOLVEDpero me pregunto si esto todavía está relacionado con este problema. De todos modos, las diferencias de comportamiento entre MyString o NSString están más allá de este contexto, pero agradecería si alguien resolviera este problema.

Kaz Yoshikawa
fuente
He adoptado esta solución para mi proyecto. ¡Gran trabajo! Solo una sugerencia, esta solución no parece eliminar nilvalores de lo interno Set. Así que agregué una reap()función mencionada en la respuesta superior y me aseguré de llamar reap()cada vez que WeakObjectSetse accede a ella.
gokeji
Hmm, espera, por alguna razón esto no funciona en Swift 4 / iOS 11. Parece que la referencia débil no se desasigna de inmediato cuando el valor se vuelve nilmás
gokeji
1
Actualicé el código a Swift4, vea la segunda mitad de la respuesta. Parece que NSString tiene algunos problemas de desasignación, pero aún debería funcionar en sus objetos de clase personalizados.
Kaz Yoshikawa
¡Muchas gracias por investigarlo @KazYoshikawa y actualizar la respuesta! También me di cuenta más tarde que una clase personalizada funciona, mientras NSStringque no.
gokeji
2
He hecho la experiencia de que el puntero que devuelve UnsafeMutablePointer<T>(&object)puede cambiar aleatoriamente (lo mismo con withUnsafePointer). Ahora uso una versión respaldada por NSHashTable: gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d .
simonseyer
12

Esta no es mi solución. Lo encontré en los foros de desarrolladores de Apple .

@GoZoner tiene una buena respuesta, pero bloquea el compilador Swift.

Aquí hay una versión de un contenedor de objetos débiles que no bloquea el compilador actual.

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

    init (value: T) {
        _value = value
    }

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

Luego puede crear una matriz de estos contenedores:

let myArray: Array<WeakContainer<MyClass>> = [myObject1, myObject2]
rjkaplan
fuente
1
extraño, pero ya no funciona con estructuras. Dice EXC_BAD_ACCESSpor mi. Con la clase funciona bien
mente
66
Las estructuras son tipos de valor, no debería funcionar con ellas. El hecho de que se bloqueó en tiempo de ejecución en lugar de ser un error en tiempo de compilación es un error del compilador.
David Goodine
10

Puede hacer esto creando un objeto contenedor para mantener un puntero débil.

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

Y luego usarlos en la matriz

var weakThings = WeakThing<Foo>[]()
Joshua Weinberg
fuente
Tiene que ser un vars classpara usarweak
Bill
3
¿Dice quién? El código anterior funciona bien para mí. El único requisito es que el objeto que se debilita debe ser una clase, no el objeto que tiene la referencia débil
Joshua Weinberg
Lo siento. Podría haber jurado que recibí un mensaje del compilador que decía "No se pueden usar variables débiles en estructuras". Tienes razón, eso compila.
Bill
55
@JoshuaWeinberg ¿y si Foo es un protocolo?
onmyway133
@ onmyway133 AFAIK si el protocolo se declara implementado solo por clases, funcionaría. protocol Protocol : class { ... }
olejnjak
8

¿Qué tal un envoltorio de estilo funcional?

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)

Simplemente llame al cierre devuelto para verificar que el objetivo aún esté vivo.

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

Y puede almacenar estos cierres en una matriz.

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

Y puede recuperar los valores débilmente capturados mediante el mapeo llamando a los cierres.

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

En realidad, no necesita una función para hacer un cierre. Simplemente captura un objeto directamente.

let captured3 = { [weak obj3] in return obj3 }
eonil
fuente
3
La pregunta es cómo crear una matriz (o decir Conjunto) de objetos débiles.
David H
Con esta solución, incluso puede crear una matriz con múltiples valores como var array: [(x: Int, y: () -> T?)]. Exactamente lo que estaba buscando.
jboi
1
@DavidH Actualicé mi respuesta para responder la pregunta. Espero que esto ayude.
eonil
Me encantó este enfoque, y creo que es súper inteligente. Hice una implementación de clase usando esta estrategia. ¡Gracias!
Ale Ravasio
No estoy muy seguro sobre el let values = Array(array1.map({ $0() })) part. Como ya no se trata de una matriz de cierres con referencias débiles, los valores se conservarán hasta que esta matriz se desasigne. Si estoy en lo correcto, entonces es importante tener en cuenta que nunca debe conservar esta matriz, self.items = Array(array1.map({ $0() }))ya que esto supera el propósito.
Matic Oblak
7

Tuve la misma idea de crear un contenedor débil con genéricos.
Como resultado, creé un contenedor para 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?
        }
    }
}

Uso:

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()
}

No es la mejor solución, ya que WeakSetse puede inicializar con cualquier tipo, y si este tipo no se ajusta al AnyObjectprotocolo, la aplicación se bloqueará con un motivo detallado. Pero no veo ninguna solución mejor en este momento.

La solución original era definir WeakSetde esta manera:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

Pero en este caso WeakSetno se puede inicializar con el protocolo:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

Actualmente, el código anterior no se puede compilar (Swift 2.1, Xcode 7.1).
Es por eso que dejé de conformarme AnyObjecty agregué guardias adicionales con fatalError()afirmaciones.

Vlad Papko
fuente
Huh solo usa para objeto en
hashtable.allObjects
6

Detalles

  • Swift 5.1, Xcode 11.3.1

Solución

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

Opción 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) }
    }
}

Opción 1 uso

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

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

opcion 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) })
    }
}

Opción 2 uso

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

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

Muestra completa

no olvides pegar el código de la solución

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")
    }
}
Vasily Bodnarchuk
fuente
Mi problema con ambas opciones (y muchas otras) es que estos tipos de matriz no se pueden usar con protocolos. Por ejemplo, esto no compilará:protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
Matic Oblak
@MaticOblak ¿qué pasa con el uso de genéricos? protocol TP: class { } class TC<TYPE> where TYPE: TP { var a = WeakObjectsArray<TYPE>() // Use like regular array. With any objects var weakObjectsArray = [TYPE?]() }
Vasily Bodnarchuk
La idea es que esta matriz puede contener objetos de diferentes tipos que implementan el mismo protocolo de clase. Al usar un genérico, lo bloquea en un solo tipo. Por ejemplo, imagine tener un singleton que contenga una matriz como delegates. Entonces, tendrá una cantidad de controladores de vista que desearían usar esta funcionalidad. Esperarías llamar MyManager.delegates.append(self). Pero si MyManagerestá bloqueado en algún tipo genérico, entonces esto no es muy útil.
Matic Oblak
@MaticOblak ok. Pruebe esto: 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())
Vasily Bodnarchuk
Ahora perdió la parte genérica con la matriz, que es un poco importante :) Tengo la sensación de que esto simplemente no es factible. Una limitación de Swift por ahora ...
Matic Oblak
4

El ejemplo existente de WeakContainer es útil, pero en realidad no ayuda a usar referencias débiles en contenedores rápidos existentes como Listas y Diccionarios.

Si desea utilizar métodos de Lista, como contiene, entonces WeakContainer deberá implementar Equatable. Así que agregué el código para permitir que WeakContainer sea equiparable.

En caso de que quisieras usar el WeakContainer en los diccionarios, también lo hice utilizable para que pueda usarse como teclas de diccionario.

También lo renombré a WeakObject para enfatizar que esto es solo para tipos de clase y para diferenciarlo de los ejemplos de 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!
}

Esto le permite hacer algunas cosas interesantes como usar un Diccionario de referencias débiles:

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)
}
Tod Cunningham
fuente
3

Aquí está cómo hacer @ gran respuesta de GoZoner conforme con Hashable, por lo que puede ser indexado en los objetos contenedores como: Set, Dictionary, Array, etc.

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
fuente
3

Como NSPointerArrayya maneja la mayor parte de esto automáticamente, resolví el problema haciendo un envoltorio de tipo seguro para ello, lo que evita muchas de las repeticiones en otras respuestas:

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
}

Ejemplo de uso:

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

Es más trabajo por adelantado, pero el uso en el resto de su código es mucho más limpio IMO. Si desea hacerlo más parecido a una matriz, incluso puede implementar suscripciones, hacer que sea un SequenceTypeetc. (pero mi proyecto solo necesita appendy, forEachpor lo tanto, no tengo el código exacto disponible).

John Montgomery
fuente
2

Otra solución más para el mismo problema ... el enfoque de este está en almacenar una referencia débil a un objeto, pero también le permite almacenar una estructura.

[No estoy seguro de lo útil que es, pero tardó un tiempo en obtener la sintaxis correcta]

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
Dan Rosenstark
fuente
1

Otras respuestas han cubierto el ángulo de los genéricos. Pensé que compartiría un código simple que cubra el nilángulo.

Quería una matriz estática (leer ocasionalmente) de todos los Labelcorreos electrónicos que existen actualmente en la aplicación, pero no quería vernil dónde solían estar los viejos.

Nada de lujos, este es mi código ...

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
fuente
¿Qué pasa con el uso en flatMaplugar de filter& map?
Lukas Kubanek
0

Basé esto en el trabajo de @Eonil, ya que me encantó la estrategia de cierre de enlace débil, pero no quería usar un operador de función para una variable, ya que me pareció extremadamente intuitivo

Lo que hice, en cambio, es lo siguiente:

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
        }
    }
}

De esta manera puedes hacer algo como:

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
Ale Ravasio
fuente
0

Esta es mi solución:

  • Limpie la matriz cuando se desasigna , porque WeakObjectSet está almacenando y no se está bajando de WeakObject
  • Resuelva el error fatal cuando se encuentra un elemento duplicado en Set

-

// 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
fuente
0

Esta es una colección segura de tipo que contiene contenedores de objetos débiles. También elimina automáticamente los contenedores / envoltorios cuando se accede.

Ejemplo:

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() }
    }
}

La colección personalizada 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 }
    }
}
Dan
fuente
0

¿Qué pasa con un enfoque funcional ?

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

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

Esta es la idea principal, luego agregue cualquier lógica de conveniencia para realizar un seguimiento de lo que hay en la matriz. Por ejemplo, uno podría considerar el mismo enfoque con un Diccionario usando la clave como una forma de encontrar lo que hay allí.

frouo
fuente