Come posso dichiarare una matrice di riferimenti deboli in Swift?


179

Vorrei archiviare una serie di riferimenti deboli in Swift. L'array stesso non dovrebbe essere un riferimento debole - i suoi elementi dovrebbero esserlo. Penso che Cocoa NSPointerArrayoffra una versione non tipografica di questo.


1
Che ne dici di creare un oggetto contenitore che fa debolmente riferimento a un altro oggetto, quindi di crearne uno array? (Se non ottieni una risposta migliore)
nielsbot

1
perché non usi un NSPointerArray?
Bastian,

@nielsbot Questa è una vecchia soluzione obj-c :) Per renderlo Swifty, dovrebbe essere un oggetto generico! :) Tuttavia, il vero problema è come rimuovere gli oggetti dall'array quando l'oggetto di riferimento viene deallocato.
Sulthan,

2
Bene, preferirei qualcosa con tipi parametrizzati. Immagino di poter creare un wrapper con parametri attorno a NSPointerArray, ma volevo vedere se c'erano alternative.
Bill,

6
Proprio come un'altra opzione, esiste NSHashTable. È fondamentalmente un NSSet che ti consente di specificare come dovrebbe fare riferimento agli oggetti che contiene.
Mick MacCallum,

Risposte:


154

Crea un wrapper generico come:

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

Aggiungi istanze di questa classe al tuo array.

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

Quando si definisce Weakè possibile utilizzare structo class.

Inoltre, per aiutare a raccogliere i contenuti dell'array, è possibile fare qualcosa sulla falsariga di:

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

L'uso di cui AnyObjectsopra dovrebbe essere sostituito con T- ma non credo che l'attuale linguaggio Swift consenta un'estensione definita come tale.


11
Come si rimuovono gli oggetti wrapper dall'array quando il loro valore è deallocato?
Sulthan,

9
Sì, si è bloccato il compilatore.
GoZoner,

5
Si prega di inserire il codice del problema in una nuova domanda; nessun motivo per dare la mia risposta quando potrebbe essere il tuo codice!
GoZoner,

2
@EdGamble Il codice fornito funziona così com'è, ma non riesce se si sostituisce la classe Stuffcon un protocollo; vedi questa domanda correlata
Theo,

2
Una struttura sarebbe migliore, poiché sarebbe mantenuta in pila invece di richiedere un heap fetch.
KPM,

60

È possibile utilizzare NSHashTable con weakObjectsHashTable. NSHashTable<ObjectType>.weakObjectsHashTable()

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

Riferimento di classe NSHashTable

Disponibile in OS X v10.5 e versioni successive.

Disponibile in iOS 6.0 e versioni successive.


Migliore risposta e non tempo di vita per i wrapper!
Ramis,

1
Questo è intelligente, ma come la risposta di GoZoner, questo non funziona con tipi che sono Anyma non AnyObject, come i protocolli.
Aaron Brager,

@SteveWilford Ma un protocollo può essere implementato da una classe, che lo renderebbe un tipo di riferimento
Aaron Brager

4
un protocollo può estendere la classe e quindi puoi usarlo come debole (ad es. protocollo MyProtocol: classe)
Yasmin Tiomkin

1
Ottengo un errore del compilatore con MyProtocol: classe NSHashTable<MyProtocol>.weakObjects(). "'NSHashTable' richiede che 'MyProtocol' sia un tipo di classe.
Greg

14

È un po 'tardi per la festa, ma prova il mio. Ho implementato come un set non un array.

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]

Attenzione che WeakObjectSet non accetta il tipo di stringa ma NSString. Perché, il tipo di stringa non è AnyType. La mia versione rapida è Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

Il codice può essere preso da Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** AGGIUNTO A NOV.2017

Ho aggiornato il codice su 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) })
    }
}

Come menzionato da Gokeji, ho capito che NSString non verrà deallocato in base al codice in uso. Mi sono grattato la testa e ho scritto la lezione di MyString come segue.

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

Quindi sostituire NSStringcon in MyStringquesto modo. Quindi strano dire che funziona.

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]

Poi ho scoperto che una strana pagina potrebbe essere correlata a questo problema.

Il riferimento debole mantiene NSString deallocato (solo XC9 + iOS Sim)

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

Dice che il problema è, RESOLVEDma mi chiedo se questo è ancora correlato a questo problema. Ad ogni modo, le differenze di comportamento tra MyString o NSString vanno oltre questo contesto, ma apprezzerei se qualcuno capisse questo problema.


Ho adottato questa soluzione per il mio progetto. Ottimo lavoro! Solo un suggerimento, questa soluzione non sembra rimuovere i nilvalori dall'interno Set. Quindi ho aggiunto una reap()funzione menzionata nella risposta in alto e mi sono assicurato di chiamare reap()ogni volta che WeakObjectSetsi accede.
Gokeji,

Aspetta, per qualche motivo questo non funziona in Swift 4 / iOS 11. Sembra che il riferimento debole non venga deallocato immediatamente quando il valore diventa nilpiù
gokeji

1
Ho aggiornato il codice su Swift4, vedi la seconda metà della risposta. Sembra che NSString abbia alcuni problemi di deallocazione, ma dovrebbe comunque funzionare sugli oggetti di classe personalizzati.
Kaz Yoshikawa,

Grazie mille per averci esaminato @KazYoshikawa e aver aggiornato la risposta! In seguito ho anche capito che una classe personalizzata funziona, mentre NSStringnon lo è.
Gokeji,

2
Ho fatto l'esperienza che il puntatore che viene restituito UnsafeMutablePointer<T>(&object)può cambiare casualmente (lo stesso con withUnsafePointer). Ora uso una versione supportata da a NSHashTable: gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d .
simonseyer,

12

Questa non è la mia soluzione. L'ho trovato sui forum degli sviluppatori Apple .

@GoZoner ha una buona risposta, ma si blocca il compilatore Swift.

Ecco una versione di un contenitore di oggetti deboli che non provoca l'arresto anomalo del compilatore attualmente rilasciato.

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

    init (value: T) {
        _value = value
    }

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

È quindi possibile creare una matrice di questi contenitori:

let myArray: Array<WeakContainer<MyClass>> = [myObject1, myObject2]

1
strano, ma non funziona più con le strutture. Dice EXC_BAD_ACCESSper me. Con la lezione funziona bene
mente,

6
Le strutture sono tipi di valore, non dovrebbero funzionare con esse. Il fatto che si sia arrestato in modo anomalo in fase di esecuzione anziché essere un errore di compilazione è un bug del compilatore.
David Goodine,

10

Puoi farlo creando un oggetto wrapper per contenere un puntatore debole.

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

E poi usandoli nell'array

var weakThings = WeakThing<Foo>[]()

Deve essere un vars da classusareweak
Bill

3
Dice chi? Il codice sopra funziona bene per me. L'unico requisito è che l'oggetto che diventa debole deve essere una classe, non l'oggetto che contiene il riferimento debole
Joshua Weinberg,

Scusate. Avrei potuto giurare di aver appena ricevuto un messaggio del compilatore che diceva "Impossibile utilizzare variabili deboli nelle strutture". Hai ragione - questo si compila.
Bill

5
@JoshuaWeinberg e se Foo fosse un protocollo?
onmyway133,

@ onmyway133 AFAIK se il protocollo fosse dichiarato implementato solo da classi funzionerebbe. protocol Protocol : class { ... }
olejnjak,

8

Che ne dite di wrapper stile funzionale?

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)

Basta chiamare la chiusura restituita per verificare che l'obiettivo sia ancora vivo.

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

E puoi archiviare queste chiusure in un array.

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

E puoi recuperare i valori catturati debolmente mappando chiamando le chiusure.

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

In realtà, non è necessaria una funzione per chiudere. Basta catturare direttamente un oggetto.

let captured3 = { [weak obj3] in return obj3 }

3
La domanda è come creare un array (o dire Set) di oggetti deboli.
David H,

Con questa soluzione, puoi persino creare un array con più valori come var array: [(x: Int, y: () -> T?)]. Esattamente, quello che stavo cercando.
jboi,

1
@DavidH Ho aggiornato la mia risposta per rispondere alla domanda. Spero che aiuti.
eonil,

Ho adorato questo approccio e penso che sia super intelligente. Ho fatto un'implementazione di classe usando questa strategia. Grazie!
Ale Ravasio,

Non sono troppo sicuro del let values = Array(array1.map({ $0() })) part. Poiché questa non è più una matrice di chiusure con riferimenti deboli, i valori verranno mantenuti fino a quando questa matrice non viene deallocata. Se ho ragione, è importante notare che non si dovrebbe mai conservare questo array come self.items = Array(array1.map({ $0() }))questo perché batte lo scopo.
Matic Oblak,

7

Ho avuto la stessa idea di creare un contenitore debole con generici.
Di conseguenza ho creato wrapper per 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()
}

Non è la soluzione migliore, perché WeakSetpuò essere inizializzata con qualsiasi tipo e se questo tipo non è conforme al AnyObjectprotocollo, l'app andrà in crash con un motivo dettagliato. Ma non vedo nessuna soluzione migliore in questo momento.

La soluzione originale era quella di definire WeakSetin questo modo:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

Ma in questo caso WeakSetnon può essere inizializzato con il protocollo:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

Al momento il codice sopra riportato non può essere compilato (Swift 2.1, Xcode 7.1).
Ecco perché ho abbandonato la conformità AnyObjecte ho aggiunto guardie aggiuntive con fatalError()affermazioni.


Huh usa solo l'oggetto in hashtable.allObjects
malhal

6

Dettagli

  • Swift 5.1, Xcode 11.3.1

Soluzione

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

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

Utilizzo dell'opzione 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) }
    }
}

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

Opzione 2 utilizzo

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

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

Campione completo

non dimenticare di incollare il codice della soluzione

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

Il mio problema con entrambe le opzioni (e molte altre) è che questi tipi di array non sono utilizzabili con i protocolli. Ad esempio, questo non verrà compilato:protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
Matic Oblak,

@MaticOblak che dire dell'uso dei generici? 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,

L'idea è che questo array può contenere oggetti di diversi tipi che implementano lo stesso protocollo di classe. Usando un generico lo blocchi in un solo tipo. Ad esempio, immagina di avere un singleton che contiene un array come delegates. Quindi avresti un certo numero di controller di visualizzazione che desiderano utilizzare questa funzionalità. Ti aspetteresti di chiamare MyManager.delegates.append(self). Ma se MyManagerè bloccato su un tipo generico, questo non è molto utilizzabile.
Matic Oblak,

@MaticOblak ok. Prova questo: 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 il

Ora hai perso la parte generica con l'array che è un po 'importante :) Ho la sensazione che questo non sia fattibile. Una limitazione di Swift per ora ...
Matic Oblak,

4

L'esempio esistente di WeakContainer è utile, ma in realtà non aiuta a usare riferimenti deboli in contenitori rapidi esistenti come Elenchi e Dizionari.

Se si desidera utilizzare i metodi Elenco come contiene, WeakContainer dovrà implementare Equatable. Quindi ho aggiunto il codice per consentire a WeakContainer di essere equabile.

Nel caso in cui volessi usare WeakContainer nei dizionari, l'ho anche reso hash per poterlo usare come chiavi del dizionario.

L'ho anche rinominato in WeakObject per sottolineare che questo è solo per i tipi di classe e per differenziarlo dagli esempi di 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!
}

Questo ti permette di fare cose interessanti come usare un Dizionario di riferimenti deboli:

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

Ecco come fare @ grande risposta di GoZoner conforme alle Hashable, in modo che possa essere indicizzato nel contenitore di oggetti come: 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
}

3

Dato che NSPointerArraygià gestisce la maggior parte di questo automaticamente, ho risolto il problema creando un involucro sicuro per il tipo, che evita molta della piastra di caldaia in altre risposte:

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
}

Esempio di utilizzo:

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

È più impegnativo, ma l'utilizzo nel resto del codice è molto più pulito dell'IMO. Se vuoi renderlo più simile a un array, puoi persino implementare la sottoscrizione, renderlo un SequenceType, ecc. (Ma il mio progetto ha solo bisogno appende forEachquindi non ho il codice esatto a portata di mano).


2

Ancora un'altra soluzione allo stesso problema ... il focus di questo è sulla memorizzazione di un debole riferimento a un oggetto ma che consente anche di memorizzare una struttura.

[Non sono sicuro di quanto sia utile, ma ci è voluto del tempo per ottenere la sintassi corretta]

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

Altre risposte hanno coperto l'angolo dei generici. Ho pensato di condividere un semplice codice che copre l' nilangolo.

Volevo un array statico (letto di tanto in tanto) di tutti gli elementi Labelattualmente presenti nell'app, ma non volevo vedere nildove si trovavano quelli vecchi.

Niente di speciale, questo è il mio codice ...

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

Che ne dici di usare flatMapinvece di filter& map?
Lukas Kubanek,

0

Ho basato questo sul lavoro di @Eonil, dal momento che ho adorato la strategia di chiusura del legame debole, ma non volevo usare un operatore di funzione per una variabile, poiché mi sembrava estremamente controintuitivo

Quello che ho fatto, invece, è il seguente:

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

In questo modo puoi fare qualcosa come:

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

Questa è la mia soluzione:

  • Ripulisce l'array quando deallocato , poiché WeakObjectSet sta memorizzando e non sta uscendo da WeakObject
  • Risolve l'errore irreversibile quando viene trovato un elemento duplicato in 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)) }
    }
}

0

Questa è una raccolta sicura di tipi che contiene contenitori di oggetti deboli. Rimuove anche automaticamente i contenitori / involucri nulli quando vi si accede.

Esempio:

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 collezione personalizzata 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

Che dire di un approccio funzionale ?

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

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

Questa è l'idea principale, quindi aggiungi qualsiasi logica di praticità per tenere traccia di ciò che è nell'array. Ad esempio, si potrebbe considerare lo stesso approccio con un dizionario usando la chiave come un modo per trovare cosa c'è dentro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.