Wie deklariere ich eine Reihe schwacher Referenzen in Swift?

179

Ich möchte eine Reihe schwacher Referenzen in Swift speichern. Das Array selbst sollte keine schwache Referenz sein - seine Elemente sollten es sein. Ich denke, Cocoa NSPointerArraybietet eine nicht typsichere Version davon.

Rechnung
quelle
1
Wie wäre es, ein Containerobjekt zu erstellen, das schwach auf ein anderes Objekt verweist, und dann ein Array davon zu erstellen? (Wenn Sie keine bessere Antwort bekommen)
Nielsbot
1
Warum benutzt du kein NSPointerArray?
Bastian
@nielsbot Das ist eine alte obj-c Lösung :) Um es schnell zu machen, sollte es ein generisches Objekt sein! :) Das eigentliche Problem besteht jedoch darin, wie Objekte aus dem Array entfernt werden, wenn die Zuordnung des referenzierten Objekts aufgehoben wird.
Sulthan
2
Richtig, ich würde etwas mit parametrisierten Typen bevorzugen. Ich denke, ich könnte einen parametrisierten Wrapper um NSPointerArray erstellen, wollte aber sehen, ob es Alternativen gibt.
Bill
6
Genau wie eine andere Option existiert NSHashTable. Grundsätzlich handelt es sich um ein NSSet, mit dem Sie festlegen können, wie auf die darin enthaltenen Objekte verwiesen werden soll.
Mick MacCallum

Antworten:

154

Erstellen Sie einen generischen Wrapper als:

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

Fügen Sie Ihrem Array Instanzen dieser Klasse hinzu.

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

Bei der Definition können WeakSie entweder structoder verwenden class.

Um das Ernten von Array-Inhalten zu erleichtern, können Sie Folgendes tun:

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

Die Verwendung von AnyObjectoben sollte durch ersetzt werden T- aber ich denke nicht, dass die aktuelle Swift-Sprache eine als solche definierte Erweiterung zulässt.

GoZoner
quelle
11
Wie entfernen Sie die Wrapper-Objekte aus dem Array, wenn ihr Wert freigegeben wird?
Sulthan
9
Ja, der Compiler ist abgestürzt.
GoZoner
5
Bitte posten Sie Ihren Problemcode in einer neuen Frage. kein Grund zum dinge meiner Antwort , wenn es könnte Ihr Code sein!
GoZoner
2
@EdGamble Der bereitgestellte Code funktioniert wie er ist, schlägt jedoch fehl, wenn Sie die Klasse Stuffdurch ein Protokoll ersetzen . siehe diese verwandte Frage
Theo
2
Eine Struktur wäre besser, da sie auf dem Stapel gehalten würde, anstatt einen Heap-Abruf zu benötigen.
KPM
60

Sie können die NSHashTable mit schwachObjectsHashTable verwenden. NSHashTable<ObjectType>.weakObjectsHashTable()

Für Swift 3: NSHashTable<ObjectType>.weakObjects()

NSHashTable-Klassenreferenz

Verfügbar in OS X 10.5 und höher.

Verfügbar in iOS 6.0 und höher.

Thierry
quelle
Beste Antwort und keine Taillenzeit für Wrapper!
Ramis
1
Das ist klug, aber wie die Antwort von GoZoner funktioniert dies nicht mit Typen, die es sind, Anyaber nicht AnyObject, wie Protokolle.
Aaron Brager
@SteveWilford Aber ein Protokoll kann von einer Klasse implementiert werden, was es zu einem Referenztyp machen würde
Aaron Brager
4
Ein Protokoll kann die Klasse erweitern und Sie können es dann als schwach verwenden (z. B. Protokoll MyProtocol: class)
Yasmin Tiomkin
1
Ich erhalte einen Compilerfehler mit MyProtocol: classund NSHashTable<MyProtocol>.weakObjects(). "'NSHashTable' erfordert, dass 'MyProtocol' ein Klassentyp ist.
Greg
14

Es ist etwas spät für die Party, aber probieren Sie meine. Ich habe als Set kein Array implementiert.

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

Verwendung

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]

Beachten Sie, dass WeakObjectSet keinen String-Typ, sondern NSString verwendet. Weil der String-Typ kein AnyType ist. Meine schnelle Version ist Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

Code kann von Gist abgerufen werden. https://gist.github.com/codelynx/30d3c42a833321f17d39

** HINZUGEFÜGT IM NOV.2017

Ich habe den Code auf Swift 4 aktualisiert

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

Wie Gokeji erwähnte, habe ich herausgefunden, dass NSString aufgrund des verwendeten Codes nicht freigegeben wird. Ich kratzte mir am Kopf und schrieb die MyString-Klasse wie folgt.

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

Dann ersetzen Sie NSStringmit MyStringso. Dann seltsam zu sagen, dass es funktioniert.

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]

Dann fand ich eine seltsame Seite, die möglicherweise mit diesem Problem zusammenhängt.

Schwache Referenz behält freigegebenen NSString bei (nur XC9 + iOS Sim)

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

Es heißt, das Problem sei, RESOLVEDaber ich frage mich, ob dies noch mit diesem Problem zusammenhängt. Wie auch immer, Verhaltensunterschiede zwischen MyString oder NSString liegen außerhalb dieses Kontexts, aber ich würde mich freuen, wenn jemand dieses Problem herausfinden würde.

Kaz Yoshikawa
quelle
Ich habe diese Lösung für mein Projekt übernommen. Gut gemacht! Nur ein Vorschlag, diese Lösung scheint keine nilWerte aus dem internen zu entfernen Set. Deshalb habe ich eine reap()in der oberen Antwort erwähnte Funktion hinzugefügt und sichergestellt, dass sie bei reap()jedem WeakObjectSetZugriff aufgerufen wird.
Gokeji
Hmm, warte, aus irgendeinem Grund funktioniert dies in Swift 4 / iOS 11 nicht. Scheint, als würde die schwache Referenz nicht sofort freigegeben, wenn der Wert nilnicht mehr wird
gokeji
1
Ich habe den Code auf Swift4 aktualisiert, siehe die zweite Hälfte der Antwort. Ich habe den Eindruck, dass NSString einige Probleme mit der Freigabe hat, aber es sollte weiterhin für Ihre benutzerdefinierten Klassenobjekte funktionieren.
Kaz Yoshikawa
Vielen Dank, dass Sie sich @KazYoshikawa angesehen und die Antwort aktualisiert haben! Ich habe später auch festgestellt, dass eine benutzerdefinierte Klasse funktioniert, während NSStringdies nicht der Fall ist.
Gokeji
2
Ich habe die Erfahrung gemacht, dass sich der Zeiger, der von zurückgegeben wird UnsafeMutablePointer<T>(&object), zufällig ändern kann (dasselbe gilt für withUnsafePointer). Ich verwende jetzt eine Version, die von a unterstützt wird NSHashTable: gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d .
Simonseyer
12

Das ist nicht meine Lösung. Ich habe es in den Apple Developer Forums gefunden .

@GoZoner hat eine gute Antwort, aber es stürzt den Swift-Compiler ab.

Hier ist eine Version eines Containers für schwache Objekte, die den aktuell veröffentlichten Compiler nicht zum Absturz bringt.

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

    init (value: T) {
        _value = value
    }

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

Sie können dann ein Array dieser Container erstellen:

let myArray: Array<WeakContainer<MyClass>> = [myObject1, myObject2]
rjkaplan
quelle
1
seltsam, funktioniert aber nicht mehr mit Strukturen. Sagt EXC_BAD_ACCESSfür mich. Mit Klasse funktioniert gut
mente
6
Strukturen sind Werttypen, sie sollten mit ihnen nicht funktionieren. Die Tatsache, dass es zur Laufzeit abgestürzt ist und kein Fehler zur Kompilierungszeit ist, ist ein Compiler-Fehler.
David Goodine
10

Sie können dies tun, indem Sie ein Wrapper-Objekt erstellen, das einen schwachen Zeiger enthält.

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

Und diese dann im Array verwenden

var weakThings = WeakThing<Foo>[]()
Joshua Weinberg
quelle
Muss ein sein class, um weakvars zu benutzen
Bill
3
Sagt wer? Der obige Code funktioniert gut für mich. Die einzige Voraussetzung ist, dass das Objekt, das schwach wird, eine Klasse sein muss, nicht das Objekt, das die schwache Referenz enthält
Joshua Weinberg
Es tut uns leid. Ich hätte schwören können, dass ich gerade eine Compilermeldung erhalten habe, die besagt, dass "schwache Variablen in Strukturen nicht verwendet werden können". Du hast recht - das kompiliert.
Bill
5
@JoshuaWeinberg was ist, wenn Foo ein Protokoll ist?
onmyway133
@ onmyway133 AFAIK Wenn das Protokoll nur von Klassen implementiert werden soll, würde es funktionieren. protocol Protocol : class { ... }
Olejnjak
8

Wie wäre es mit funktionalem Wrapper?

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)

Rufen Sie einfach die zurückgegebene Schließung an, um zu überprüfen, ob das Ziel noch am Leben ist.

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

Und Sie können diese Verschlüsse in einem Array speichern.

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

Und Sie können die schwach erfassten Werte abrufen, indem Sie die Aufrufe der Schließungen zuordnen.

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

Eigentlich brauchen Sie keine Funktion, um einen Abschluss zu machen. Erfassen Sie einfach ein Objekt direkt.

let captured3 = { [weak obj3] in return obj3 }
Eonil
quelle
3
Die Frage ist, wie man ein Array (oder eine Menge) schwacher Objekte erstellt.
David H
Mit dieser Lösung können Sie sogar ein Array mit mehreren Werten wie erstellen var array: [(x: Int, y: () -> T?)]. Genau das, wonach ich gesucht habe.
Jboi
1
@ DavidH Ich habe meine Antwort aktualisiert, um die Frage zu beantworten. Ich hoffe das hilft.
Eonil
Ich habe diesen Ansatz geliebt und finde ihn super clever. Mit dieser Strategie habe ich eine Klassenimplementierung durchgeführt. Danke dir!
Ale Ravasio
Ich bin mir nicht sicher let values = Array(array1.map({ $0() })) part. Da dies kein Array von Abschlüssen mit schwachen Referenzen mehr ist, bleiben die Werte erhalten, bis die Zuordnung dieses Arrays aufgehoben wird. Wenn ich richtig liege, ist es wichtig zu beachten, dass Sie dieses Array niemals so beibehalten sollten, self.items = Array(array1.map({ $0() }))da dies den Zweck übertrifft.
Matic Oblak
7

Ich hatte die gleiche Idee, einen schwachen Container mit Generika zu erstellen.
Als Ergebnis habe ich einen Wrapper erstellt für 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?
        }
    }
}

Verwendung:

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

Es ist nicht die beste Lösung, da WeakSetes mit jedem Typ initialisiert werden kann. Wenn dieser Typ nicht dem AnyObjectProtokoll entspricht, stürzt die App aus detaillierten Gründen ab. Aber ich sehe momentan keine bessere Lösung.

Die ursprüngliche Lösung bestand darin, Folgendes zu definieren WeakSet:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

In diesem Fall WeakSetkann jedoch nicht mit dem Protokoll initialisiert werden:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

Derzeit kann der obige Code nicht kompiliert werden (Swift 2.1, Xcode 7.1).
Aus diesem Grund habe ich die Anpassung eingestellt AnyObjectund zusätzliche Wachen mit fatalError()Behauptungen hinzugefügt .

Vlad Papko
quelle
Huh nur für Objekt in
hashtable.allObjects
6

Einzelheiten

  • Swift 5.1, Xcode 11.3.1

Lösung

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

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

Option 1 Verwendung

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

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

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

Option 2 Verwendung

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

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

Vollständige Probe

Vergessen Sie nicht, den Lösungscode einzufügen

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")
    }
}
Wassili Bodnarchuk
quelle
Mein Problem mit beiden Optionen (und vielen anderen) ist, dass diese Array-Typen nicht mit Protokollen verwendet werden können. Zum Beispiel wird dies nicht kompiliert:protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
Matic Oblak
@MaticOblak was ist mit Generika? 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
Die Idee ist, dass dieses Array Objekte unterschiedlichen Typs enthalten kann, die dasselbe Klassenprotokoll implementieren. Wenn Sie ein Generikum verwenden, sperren Sie es auf einen einzelnen Typ. Stellen Sie sich zum Beispiel einen Singleton vor, der ein Array wie enthält delegates. Dann hätten Sie einige View Controller, die diese Funktionalität nutzen möchten. Sie würden erwarten, anzurufen MyManager.delegates.append(self). Wenn MyManageres jedoch an einen generischen Typ gebunden ist, ist dies nicht sehr brauchbar.
Matic Oblak
@MaticOblak ok. Versuchen Sie Folgendes: 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
Sie haben jetzt den generischen Teil mit dem Array verloren, was ein bisschen wichtig ist :) Ich habe das Gefühl, dass dies einfach nicht machbar ist. Eine Einschränkung von Swift für jetzt ...
Matic Oblak
4

Das vorhandene Beispiel für den WeakContainer ist hilfreich, aber es hilft nicht wirklich, schwache Referenzen in vorhandenen schnellen Containern wie Listen und Wörterbüchern zu verwenden.

Wenn Sie List-Methoden wie Contain verwenden möchten, muss der WeakContainer Equatable implementieren. Also habe ich den Code hinzugefügt, damit der WeakContainer gleichwertig ist.

Für den Fall, dass Sie den WeakContainer in Wörterbüchern verwenden möchten, habe ich ihn auch hashbar gemacht, damit er als Wörterbuchschlüssel verwendet werden kann.

Ich habe es auch in WeakObject umbenannt, um zu betonen, dass dies nur für Klassentypen gilt, und um es von den WeakContainer-Beispielen zu unterscheiden:

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

Auf diese Weise können Sie einige coole Dinge tun, z. B. ein Wörterbuch mit schwachen Referenzen verwenden:

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

Hier ist , wie @ GoZoner die große Antwort machen entsprechen Hashable, so dass es indiziert werden können in Container - Objekte wie: Set, Dictionary, Arrayetc.

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

Da NSPointerArraydas meiste davon bereits automatisch erledigt wird, habe ich das Problem gelöst, indem ich einen typsicheren Wrapper dafür erstellt habe, der in anderen Antworten viel von der Boilerplate vermeidet:

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
}

Anwendungsbeispiel:

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 ist mehr Arbeit im Voraus, aber die Verwendung im Rest Ihres Codes ist IMO viel sauberer. Wenn Sie es Array-ähnlicher machen möchten, können Sie sogar Subskription implementieren, es zu einem machen SequenceTypeusw. (aber mein Projekt benötigt nur appendund forEachdaher habe ich nicht den genauen Code zur Hand).

John Montgomery
quelle
2

Eine weitere Lösung für das gleiche Problem ... Der Schwerpunkt dieses Problems liegt auf dem Speichern eines schwachen Verweises auf ein Objekt, aber auch auf dem Speichern einer Struktur.

[Ich bin nicht sicher, wie nützlich es ist, aber es hat eine Weile gedauert, bis die Syntax richtig war]

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
quelle
1

Andere Antworten haben den generischen Blickwinkel abgedeckt. Ich dachte, ich würde einen einfachen Code teilen, der das abdecktnil Winkel .

Ich wollte ein statisches Array (gelegentlich lesen) aller Labels, die derzeit in der App vorhanden sind, wollte es aber nicht sehennil , wo sich die alten befanden.

Nichts Besonderes, das ist mein Code ...

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
quelle
Was ist mit & flatMapanstelle von filter& map?
Lukas Kubanek
0

Ich habe dies auf die Arbeit von @Eonil gestützt, da ich die Strategie der schwachen Verschlussbindung liebte, aber ich wollte keinen Funktionsoperator für eine Variable verwenden, da sie sich äußerst kontraintuitiv anfühlte

Was ich stattdessen getan habe, ist wie folgt:

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

Auf diese Weise können Sie Folgendes tun:

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
quelle
0

Das ist meine Lösung:

  • Bereinigen Sie das Array bei der Freigabe , da WeakObjectSet WeakObject speichert und nicht verlässt
  • Beheben Sie den schwerwiegenden Fehler, wenn ein doppeltes Element in Set gefunden wurde

- -

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

Dies ist eine typsichere Sammlung, die Container mit schwachen Objekten enthält. Außerdem werden beim Zugriff automatisch keine Container / Wrapper entfernt.

Beispiel:

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

Die benutzerdefinierte Sammlung 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
quelle
0

Was ist mit einem funktionalen Ansatz ?

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

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

Dies ist die Hauptidee. Fügen Sie dann eine beliebige Komfortlogik hinzu, um zu verfolgen, was sich im Array befindet. Zum Beispiel könnte man den gleichen Ansatz mit einem Wörterbuch in Betracht ziehen, bei dem der Schlüssel verwendet wird, um herauszufinden, was sich darin befindet.

frouo
quelle