Jak zadeklarować tablicę słabych referencji w Swift?

179

Chciałbym przechowywać tablicę słabych referencji w Swift. Sama tablica nie powinna być słabym odniesieniem - jej elementami powinny być. Myślę, że Cocoa NSPointerArrayoferuje nietypową wersję tego.

Rachunek
źródło
1
A co z tworzeniem obiektu kontenera, który słabo odwołuje się do innego obiektu, a następnie tworzeniem ich tablicy? (Jeśli nie uzyskasz lepszej odpowiedzi)
nielsbot
1
dlaczego nie używasz tablicy NSPointerArray?
Bastian
@nielsbot To stare rozwiązanie obj-c :) Aby uczynić go Swifty, powinien to być obiekt ogólny! :) Jednak prawdziwy problem polega na tym, jak usunąć obiekty z tablicy, gdy odwołany obiekt jest zwolniony.
Sulthan
2
Racja, wolałbym coś ze sparametryzowanymi typami. Myślę, że mógłbym sparametryzować opakowanie wokół NSPointerArray, ale chciałem sprawdzić, czy są jakieś alternatywy.
Bill
6
Podobnie jak inna opcja, istnieje NSHashTable. Zasadniczo jest to NSSet, który pozwala określić, w jaki sposób powinien odnosić się do zawartych w nim obiektów.
Mick MacCallum,

Odpowiedzi:

154

Utwórz ogólne opakowanie jako:

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

Dodaj wystąpienia tej klasy do swojej tablicy.

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

Podczas definiowania Weakmożesz użyć jednego z nichstruct lub class.

Ponadto, aby pomóc w zbieraniu zawartości tablicy, możesz zrobić coś w stylu:

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

Zastosowanie AnyObjectpowyższego powinno zostać zastąpione przez T- ale nie sądzę, że obecny język Swift zezwala na rozszerzenie zdefiniowane jako takie.

GoZoner
źródło
11
Jak usunąć obiekty opakowania z tablicy, gdy ich wartość zostaje zwolniona?
Sulthan,
9
Tak, to spowodowało awarię kompilatora.
GoZoner,
5
Prześlij swój kod problemu w nowym pytaniu; nie ma powodu, by podawać moją odpowiedź, kiedy może to być Twój kod!
GoZoner,
2
@EdGamble Podany kod działa bez zmian, ale kończy się niepowodzeniem, jeśli zastąpisz klasę Stuffprotokołem; zobacz to powiązane pytanie
Theo
2
Struktura byłaby lepsza, ponieważ byłaby przechowywana na stosie zamiast konieczności pobierania sterty.
KPM
60

Możesz użyć NSHashTable z poorObjectsHashTable. NSHashTable<ObjectType>.weakObjectsHashTable()

W przypadku Swift 3: NSHashTable<ObjectType>.weakObjects()

Odwołanie do klasy NSHashTable

Dostępne w OS X 10.5 i nowszych.

Dostępne w iOS 6.0 i nowszych.

Thierry
źródło
Najlepsza odpowiedź i nie marnuj czasu na pakowanie!
Ramis
1
Jest to sprytne, ale podobnie jak odpowiedź GoZonera, nie działa to z typami, które są, Anyale nie AnyObject, takimi jak protokoły.
Aaron Brager
@SteveWilford Ale klasa może zaimplementować protokół, co uczyniłoby go typem referencyjnym
Aaron Brager
4
protokół może rozszerzyć klasę, a następnie można użyć go jako słabego (np. protokół MyProtocol: klasa)
Yasmin Tiomkin
1
Dostaję błąd kompilatora przy pomocy MyProtocol: classi NSHashTable<MyProtocol>.weakObjects(). „NSHashTable” wymaga, aby „MyProtocol” był typem klasy.
Greg
14

Trochę późno na imprezę, ale spróbuj mojej. Zaimplementowałem jako zestaw, a nie tablicę.

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

Stosowanie

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]

Uważaj, że WeakObjectSet nie przyjmuje typu String, ale NSString. Ponieważ typ ciągu nie jest AnyType. Moja szybka wersja to Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

Kod można pobrać z Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** DODANO W LISTOPADZIE 2017

Zaktualizowałem kod do 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) })
    }
}

Jak wspomniał gokeji, doszedłem do wniosku, że NSString nie zostanie zwolniony na podstawie używanego kodu. Porysowałem głowę i napisałem klasę MyString w następujący sposób.

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

Następnie wymień NSStringze MyStringtak. To dziwne, że to działa.

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]

Potem znalazłem dziwną stronę, która może być związana z tym problemem.

Słaba referencja zachowuje zwolniony NSString (tylko XC9 + iOS Sim)

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

Mówi, że problem jest, RESOLVEDale zastanawiam się, czy nadal jest to związane z tym problemem. W każdym razie różnice w zachowaniu między MyString lub NSString wykraczają poza ten kontekst, ale byłbym wdzięczny, gdyby ktoś wymyślił ten problem.

Kaz Yoshikawa
źródło
Przyjąłem to rozwiązanie do mojego projektu. Dobra robota! Tylko jedna sugestia, to rozwiązanie nie wydaje się usuwać nilwartości z wewnętrznych Set. Dodałem więc reap()funkcję wymienioną w górnej odpowiedzi i upewniłem się, że dzwonię przy reap()każdym WeakObjectSetdostępie.
gokeji
Hmm, czekaj, z jakiegoś powodu to nie działa w Swift 4 / iOS 11. Wydaje się, że słabe odniesienie nie zostaje nil
natychmiast zwolnione,
1
Zaktualizowałem kod do Swift4, patrz druga połowa odpowiedzi. Wydaje mi się, że NSString ma pewne problemy z dezalokacją, ale nadal powinno działać na twoich niestandardowych obiektach klasy.
Kaz Yoshikawa
Bardzo dziękuję za przyjrzenie się temu @KazYoshikawa i zaktualizowanie odpowiedzi! Później zdałem sobie również sprawę, że klasa niestandardowa działa, a NSStringnie działa.
gokeji
2
Mam wrażenie, że zwracany wskaźnik UnsafeMutablePointer<T>(&object)może zmieniać się losowo (tak samo jak withUnsafePointer). Teraz używam wersji wspieranej przez NSHashTable: gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d .
simonseyer
12

To nie jest moje rozwiązanie. Znalazłem to na forach programistów Apple .

@GoZoner ma dobrą odpowiedź, ale powoduje awarię kompilatora Swift.

Oto wersja kontenera o słabych obiektach nie powoduje awarii aktualnie wydanego kompilatora.

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

    init (value: T) {
        _value = value
    }

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

Następnie możesz utworzyć tablicę tych kontenerów:

let myArray: Array<WeakContainer<MyClass>> = [myObject1, myObject2]
rjkaplan
źródło
1
dziwne, ale nie działa już z strukturami. Mówi EXC_BAD_ACCESSza mnie Z klasą działa dobrze
mente
6
Struktury są typami wartości, nie powinny z nimi współpracować. Fakt, że zawiesił się w czasie wykonywania, a nie był błędem podczas kompilacji, jest błędem kompilatora.
David Goodine,
10

Możesz to zrobić, tworząc obiekt opakowujący do przechowywania słabego wskaźnika.

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

A następnie używając ich w tablicy

var weakThings = WeakThing<Foo>[]()
Joshua Weinberg
źródło
Musi być classdo użytku weakvars
Bill
3
Mówi kto Powyższy kod działa dla mnie dobrze. Jedynym wymaganiem jest to, że obiekt, który staje się słaby, musi być klasą, a nie obiektem posiadającym słabe odniesienie
Joshua Weinberg,
Przepraszam. Mógłbym przysiąc, że właśnie dostałem komunikat kompilatora: „Nie można używać słabych zmiennych w strukturach”. Masz rację - to się kompiluje.
Bill
5
@JoshuaWeinberg co jeśli Foo to protokół?
onmyway133
@ onmyway133 AFAIK, jeśli protokół zostanie zadeklarowany jako zaimplementowany tylko przez klasy, działałby. protocol Protocol : class { ... }
olejnjak
8

Co powiesz na funkcjonalne opakowanie?

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)

Wystarczy zadzwonić do zwrócenia zamknięcia, aby sprawdzić, czy cel nadal żyje.

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

I możesz przechowywać te zamknięcia w tablicy.

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

Możesz odzyskać słabo uchwycone wartości, odwzorowując wywołania zamknięć.

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

W rzeczywistości nie potrzebujesz funkcji do zamknięcia. Wystarczy przechwycić obiekt bezpośrednio.

let captured3 = { [weak obj3] in return obj3 }
eonil
źródło
3
Pytanie brzmi: jak stworzyć tablicę (lub powiedzieć Zestaw) słabych obiektów.
David H
Dzięki temu rozwiązaniu możesz nawet utworzyć tablicę z wieloma wartościami, takimi jak var array: [(x: Int, y: () -> T?)]. Właśnie tego szukałem.
jboi
1
@DavidH Zaktualizowałem swoją odpowiedź, aby odpowiedzieć na pytanie. Mam nadzieję, że to pomoże.
eonil
Podobało mi się to podejście i myślę, że jest super sprytne. Wykonałem klasową implementację przy użyciu tej strategii. Dziękuję Ci!
Ale Ravasio,
Nie jestem pewien co do let values = Array(array1.map({ $0() })) part. Ponieważ nie jest to już tablica zamknięć o słabych referencjach, wartości zostaną zachowane do momentu zwolnienia tej tablicy. Jeśli mam rację, ważne jest, aby pamiętać, że nigdy nie powinieneś zachowywać tej tablicy, self.items = Array(array1.map({ $0() }))ponieważ jest to lepsze niż cel.
Matic Oblak,
7

Miałem ten sam pomysł, aby stworzyć słaby pojemnik z lekami generycznymi.
W rezultacie stworzyłem opakowanie dla 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?
        }
    }
}

Stosowanie:

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

Nie jest to najlepsze rozwiązanie, ponieważ WeakSetmożna je zainicjować dowolnym typem, a jeśli ten typ nie jest zgodnyAnyObject protokołem, aplikacja ulegnie awarii ze szczegółowym powodem. Ale obecnie nie widzę lepszego rozwiązania.

Pierwotnym rozwiązaniem było zdefiniowanie WeakSetw ten sposób:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

Ale w tym przypadku WeakSetnie można zainicjować za pomocą protokołu:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

Obecnie powyższego kodu nie można skompilować (Swift 2.1, Xcode 7.1).
Dlatego zrezygnowałem z zgodności AnyObjecti dodałem dodatkowych strażników z fatalError()zapewnieniami.

Vlad Papko
źródło
Huh wystarczy użyć dla obiektu w hashtable.allObjects
malhal
6

Detale

  • Swift 5.1, Xcode 11.3.1

Rozwiązanie

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

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

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

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

Wykorzystanie opcji 2

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

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

Pełna próbka

nie zapomnij wkleić kodu rozwiązania

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")
    }
}
Wasilij Bodnarchuk
źródło
Mój problem z obiema opcjami (i wieloma innymi) polega na tym, że tego typu tablice nie nadają się do użycia z protokołami. Na przykład nie będzie to kompilować:protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
Matic Oblak,
@MaticOblak co z używaniem leków generycznych? protocol TP: class { } class TC<TYPE> where TYPE: TP { var a = WeakObjectsArray<TYPE>() // Use like regular array. With any objects var weakObjectsArray = [TYPE?]() }
Wasilij Bodnarchuk
Chodzi o to, że ta tablica może przechowywać obiekty różnych typów, które implementują ten sam protokół klasy. Używając generycznego, blokujesz go do jednego typu. Wyobraź sobie na przykład singletona zawierającego taką tablicę jak delegates. Wtedy miałbyś pewną liczbę kontrolerów widoku, którzy chcieliby korzystać z tej funkcji. Spodziewałbyś się zadzwonić MyManager.delegates.append(self). Ale jeśli MyManagerjest zablokowany na jakimś ogólnym typie, nie jest to zbyt użyteczne.
Matic Oblak,
@MaticOblak ok. Spróbuj tego: 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())
Wasilij Bodnarchuk
Straciłeś teraz część ogólną z tablicą, co jest trochę ważne :) Mam wrażenie, że to po prostu niemożliwe. Na razie ograniczenie Swift ...
Matic Oblak
4

Istniejący przykład WeakContainer jest pomocny, ale tak naprawdę nie pomaga używać słabych referencji w istniejących szybkich kontenerach, takich jak Listy i Słowniki.

Jeśli chcesz używać metod List, takich jak zawiera, WeakContainer będzie musiał zaimplementować Equatable. Dodałem więc kod, aby umożliwić równoważenie WeakContainer.

Na wypadek, gdybyś chciał używać WeakContainer w słownikach, stworzyłem też hashable, dzięki czemu może być używany jako klucze słownika.

Zmieniłem też nazwę na WeakObject, aby podkreślić, że dotyczy to tylko typów klas i odróżnić go od przykładów 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!
}

To pozwala ci robić fajne rzeczy, takie jak Słownik słabych referencji:

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
źródło
3

Oto jak zrobić @ wielką odpowiedź GoZoner za zgodne Hashable, więc może to być indeksowane w Kontener obiektów takich jak: Set, Dictionary, Array, itd.

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
źródło
3

Ponieważ NSPointerArrayjuż obsługuje większość tego automatycznie, rozwiązałem problem, tworząc dla niego bezpieczne opakowanie, które pozwala uniknąć wielu problemów z kotłem w innych odpowiedziach:

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
}

Przykładowe użycie:

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

To więcej pracy z góry, ale użycie reszty kodu jest znacznie czystsze IMO. Jeśli chcesz, aby uczynić go bardziej podobny do tablicy, można nawet wdrożyć indeksowanie, sprawiają, że SequenceType, itd (ale tylko mój projekt musi appendi forEachtak nie mam dokładny kod na rękę).

John Montgomery
źródło
2

Jeszcze inne rozwiązanie tego samego problemu ... celem tego jest przechowywanie słabego odniesienia do obiektu, ale także umożliwienie przechowywania struktury.

[Nie jestem pewien, czy jest to przydatne, ale zajęło trochę czasu, aby uzyskać prawidłową składnię]

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
źródło
1

Inne odpowiedzi dotyczyły kąta ogólnego. Pomyślałem, że podzielę się prostym kodem obejmującym nilkąt.

Chciałem mieć statyczną tablicę (od czasu do czasu odczytywaną) wszystkich Labelaplikacji istniejących obecnie w aplikacji, ale nie chciałem widzieć nil, gdzie były stare.

Nic szczególnego, to mój kod ...

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
źródło
Co powiesz na używanie flatMapzamiast filter& map?
Lukas Kubanek
0

Oparłem to na pracy @Eonil, ponieważ podobała mi się strategia słabego wiązania zamknięcia, ale nie chciałem używać operatora funkcji dla zmiennej, ponieważ wydawało mi się to sprzeczne z intuicją

To, co zrobiłem, to:

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

W ten sposób możesz zrobić coś takiego jak:

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
źródło
0

To moje rozwiązanie:

  • Wyczyść tablicę po zwolnieniu , ponieważ WeakObjectSet przechowują i nie wychodzą z WeakObject
  • Rozwiąż błąd krytyczny, gdy w zestawie znajdzie się zduplikowany element

-

// 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
źródło
0

Jest to bezpieczna kolekcja typu, która przechowuje pojemniki ze słabymi przedmiotami. Automatycznie usuwa również zero pojemników / opakowań po uzyskaniu dostępu.

Przykład:

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

Kolekcja niestandardowa 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
źródło
0

Co z podejściem funkcjonalnym ?

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

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

Jest to główny pomysł, a następnie dodaj dowolną logikę wygody, aby śledzić zawartość tablicy. Na przykład, można rozważyć to samo podejście ze słownikiem wykorzystującym klucz jako sposób na znalezienie tego, co jest w środku.

frouo
źródło