Comment déclarer un tableau de références faibles dans Swift?

179

J'aimerais stocker un tableau de références faibles dans Swift. Le tableau lui-même ne doit pas être une référence faible - ses éléments doivent l'être. Je pense que Cocoa en NSPointerArraypropose une version non sécurisée.

Facture
la source
1
Qu'en est-il de créer un objet conteneur qui référence faiblement un autre objet, puis d'en créer un tableau? (Si vous n'obtenez pas une meilleure réponse)
nielsbot
1
pourquoi n'utilisez-vous pas un NSPointerArray?
Bastian
@nielsbot C'est une vieille solution obj-c :) Pour en faire Swifty, ce devrait être un objet générique! :) Cependant, le vrai problème est de savoir comment supprimer des objets du tableau lorsque l'objet référencé est désalloué.
Sulthan
2
Bien, je préfère quelque chose avec des types paramétrés. Je suppose que je pourrais créer un wrapper paramétré autour de NSPointerArray, mais je voulais voir s'il y avait des alternatives.
Bill
6
Tout comme une autre option, NSHashTable existe. C'est essentiellement un NSSet qui vous permet de spécifier comment il doit référencer les objets qu'il contient.
Mick MacCallum

Réponses:

154

Créez un wrapper générique comme:

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

Ajoutez des instances de cette classe à votre tableau.

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

Lors de la définition, Weakvous pouvez utiliser soit structou class.

De plus, pour aider à récolter le contenu du tableau, vous pouvez faire quelque chose du genre:

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

L'utilisation de AnyObjectci - dessus devrait être remplacée par T- mais je ne pense pas que le langage Swift actuel autorise une extension définie comme telle.

GoZoner
la source
11
Comment supprimer les objets wrapper du tableau lorsque leur valeur est désallouée?
Sulthan
9
Oui, il a planté le compilateur.
GoZoner
5
Veuillez publier votre code de problème dans une nouvelle question; aucune raison de donner ma réponse alors que ce pourrait être votre code!
GoZoner
2
@EdGamble Le code fourni fonctionne tel quel, mais échoue si vous remplacez la classe Stuffpar un protocole; voir cette question connexe
Theo
2
Une structure serait meilleure, car elle serait conservée sur la pile au lieu de nécessiter une extraction de tas.
KPM
60

Vous pouvez utiliser NSHashTable avec lowObjectsHashTable. NSHashTable<ObjectType>.weakObjectsHashTable()

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

Référence de classe NSHashTable

Disponible dans OS X v10.5 et versions ultérieures.

Disponible dans iOS 6.0 et versions ultérieures.

Thierry
la source
Meilleure réponse et ne perdez pas de temps pour les emballages!
Ramis
1
C'est intelligent, mais comme la réponse de GoZoner, cela ne fonctionne pas avec des types qui ne le sont Anypas AnyObject, tels que les protocoles.
Aaron Brager
@SteveWilford Mais un protocole peut être implémenté par une classe, ce qui en ferait un type de référence
Aaron Brager
4
un protocole peut étendre la classe et ensuite vous pouvez l'utiliser comme faible (par exemple, protocole MyProtocol: class)
Yasmin Tiomkin
1
J'obtiens une erreur de compilation avec MyProtocol: classet NSHashTable<MyProtocol>.weakObjects(). "'NSHashTable' nécessite que 'MyProtocol' soit un type de classe.
Greg
14

Il est un peu tard pour la fête, mais essayez le mien. J'ai implémenté comme un ensemble et non un tableau.

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

Usage

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]

Attention, WeakObjectSet ne prendra pas le type String mais NSString. Parce que le type String n'est pas un AnyType. Ma version rapide est Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

Le code peut être récupéré à partir de Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** AJOUTÉ EN NOV 2017

J'ai mis à jour le code vers 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) })
    }
}

Comme gokeji l'a mentionné, j'ai compris que NSString ne serait pas désalloué en fonction du code utilisé. Je me suis gratté la tête et j'ai écrit la classe MyString comme suit.

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

Puis remplacez NSStringpar MyStringcomme ça. Puis étrange de dire que cela fonctionne.

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]

Ensuite, j'ai trouvé qu'une page étrange pouvait être liée à ce problème.

La référence faible conserve la NSString désallouée (XC9 + iOS Sim uniquement)

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

Il dit que le problème est, RESOLVEDmais je me demande si cela est toujours lié à ce problème. Quoi qu'il en soit, les différences de comportement entre MyString ou NSString sont au-delà de ce contexte, mais j'apprécierais que quelqu'un trouve ce problème.

Kaz Yoshikawa
la source
J'ai adopté cette solution pour mon projet. Bon travail! Juste une suggestion, cette solution ne semble pas supprimer les nilvaleurs de l'interne Set. J'ai donc ajouté une reap()fonction mentionnée dans la première réponse, et je me suis assuré d'appeler à reap()chaque fois que le WeakObjectSetest accédé.
gokeji
Hmm, attendez, pour une raison quelconque, cela ne fonctionne pas dans Swift 4 / iOS 11. Il semble que la référence faible ne soit pas désallouée immédiatement lorsque la valeur devient nilplus
gokeji
1
J'ai mis à jour le code vers Swift4, voir la deuxième moitié de la réponse. Il semble que NSString ait des problèmes de désallocation, mais il devrait toujours fonctionner sur vos objets de classe personnalisés.
Kaz Yoshikawa
Merci beaucoup d'avoir regardé @KazYoshikawa et d'avoir mis à jour la réponse! J'ai également réalisé plus tard qu'une classe personnalisée fonctionne, alors que ce NSStringn'est pas le cas.
gokeji
2
J'ai fait l'expérience que le pointeur renvoyé par UnsafeMutablePointer<T>(&object)peut changer de manière aléatoire (même chose avec withUnsafePointer). J'utilise maintenant une version soutenue par NSHashTable: gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d .
simonseyer
12

Ce n'est pas ma solution. Je l'ai trouvé sur les forums des développeurs Apple .

@GoZoner a une bonne réponse, mais il plante le compilateur Swift.

Voici une version d'un conteneur d'objets faibles qui ne plante pas le compilateur publié actuel.

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

    init (value: T) {
        _value = value
    }

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

Vous pouvez ensuite créer un tableau de ces conteneurs:

let myArray: Array<WeakContainer<MyClass>> = [myObject1, myObject2]
rjkaplan
la source
1
étrange, mais ne fonctionne plus avec les structures. Dit EXC_BAD_ACCESSpour moi. Avec la classe fonctionne très bien
mente
6
Les structures sont des types de valeur, cela ne devrait pas fonctionner avec elles. Le fait qu'il plante au moment de l'exécution plutôt que d'être une erreur de compilation est un bogue du compilateur.
David Goodine
10

Vous pouvez le faire en créant un objet wrapper pour contenir un pointeur faible.

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

Et puis en les utilisant dans le tableau

var weakThings = WeakThing<Foo>[]()
Joshua Weinberg
la source
Doit être un classpour utiliser weakvars
Projet de loi
3
Dit qui? Le code ci-dessus fonctionne très bien pour moi. La seule exigence est que l'objet devenant faible doit être une classe, pas l'objet contenant la référence faible
Joshua Weinberg
Désolé. J'aurais juré que je viens de recevoir un message du compilateur qui disait "Ne peut pas utiliser de variables faibles dans les structs". Vous avez raison - cela compile.
Projet de loi
5
@JoshuaWeinberg et si Foo est un protocole?
onmyway133
@ onmyway133 AFAIK si le protocole est déclaré implémenté uniquement par les classes, il fonctionnerait. protocol Protocol : class { ... }
olejnjak le
8

Qu'en est-il de l'emballage de style fonctionnel?

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)

Appelez simplement la fermeture retournée pour vérifier que la cible est toujours en vie.

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

Et vous pouvez stocker ces fermetures dans un tableau.

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

Et vous pouvez récupérer les valeurs faiblement capturées en mappant l'appel des fermetures.

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

En fait, vous n'avez pas besoin d'une fonction pour faire une fermeture. Capturez simplement un objet directement.

let captured3 = { [weak obj3] in return obj3 }
éonil
la source
3
La question est de savoir comment créer un tableau (ou disons un ensemble) d'objets faibles.
David H
Avec cette solution, vous pouvez même créer un tableau avec plusieurs valeurs comme var array: [(x: Int, y: () -> T?)]. Exactement, ce que je cherchais.
jboi
1
@DavidH J'ai mis à jour ma réponse pour répondre à la question. J'espère que ça aide.
eonil le
J'ai adoré cette approche et je pense que c'est super intelligent. J'ai fait une implémentation de classe en utilisant cette stratégie. Je vous remercie!
Ale Ravasio
Pas trop sûr de la let values = Array(array1.map({ $0() })) part. Comme il ne s'agit plus d'un tableau de fermetures avec des références faibles, les valeurs seront conservées jusqu'à ce que ce tableau soit désalloué. Si j'ai raison, il est important de noter que vous ne devriez jamais conserver ce tableau comme self.items = Array(array1.map({ $0() }))cela dépasse l'objectif.
Matic Oblak le
7

J'ai eu la même idée de créer un conteneur faible avec des génériques.
En conséquence, j'ai créé un wrapper pour 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?
        }
    }
}

Usage:

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

Ce n'est pas la meilleure solution, car elle WeakSetpeut être initialisée avec n'importe quel type, et si ce type n'est pas conforme au AnyObjectprotocole, l'application plantera avec une raison détaillée. Mais je ne vois pas de meilleure solution pour le moment.

La solution originale était de définir WeakSetde cette manière:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

Mais dans ce cas, WeakSetne peut pas être initialisé avec le protocole:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

Actuellement, le code ci-dessus ne peut pas être compilé (Swift 2.1, Xcode 7.1).
C'est pourquoi j'ai abandonné la conformité AnyObjectet ajouté des gardes supplémentaires avec des fatalError()affirmations.

Vlad Papko
la source
Huh juste utiliser pour objet dans
hashtable.allObjects
6

Détails

  • Swift 5.1, Xcode 11.3.1

Solution

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

Utilisation de l'option 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) }
    }
}

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

Utilisation de l'option 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) }
    }
}

Échantillon complet

n'oubliez pas de coller le code de la solution

import UIKit

class ViewController: UIViewController {

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

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

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

extension ViewController {

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

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

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

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

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

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

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

        printArray(title: "First view deleted")
    }

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

    @objc func removeNils() {
        weakObjectsArray = weakObjectsArray.filter { $0 != nil }
        printArray(title: "Remove all nil elements in weakArray")
    }
}
Vasily Bodnarchuk
la source
Mon problème avec les deux options (et bien d'autres) est que ces types de tableaux ne sont pas utilisables avec les protocoles. Par exemple, cela ne compilera pas:protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
Matic Oblak
@MaticOblak qu'en est-il des génériques? 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'idée est que ce tableau peut contenir des objets de différents types qui implémentent le même protocole de classe. En utilisant un générique, vous le verrouillez à un seul type. Par exemple, imaginez avoir un singleton contenant un tableau tel que delegates. Ensuite, vous auriez un certain nombre de contrôleurs de vue qui aimeraient utiliser cette fonctionnalité. Vous vous attendez à appeler MyManager.delegates.append(self). Mais si MyManagerest verrouillé sur un type générique, ce n'est pas très utilisable.
Matic Oblak
@MaticOblak ok. Essayez ceci: 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
Vous avez maintenant perdu la partie générique avec le tableau qui est un peu importante :) J'ai le sentiment que ce n'est tout simplement pas faisable. Une limitation de Swift pour l'instant ...
Matic Oblak
4

L'exemple existant de WeakContainer est utile, mais il n'aide pas vraiment à utiliser des références faibles dans des conteneurs rapides existants tels que les listes et les dictionnaires.

Si vous souhaitez utiliser des méthodes List telles que contains, le WeakContainer devra implémenter Equatable. J'ai donc ajouté le code pour permettre au WeakContainer d'être égalable.

Au cas où vous voudriez utiliser le WeakContainer dans les dictionnaires, je l'ai également rendu hachable afin qu'il puisse être utilisé comme clé de dictionnaire.

Je l'ai également renommé WeakObject pour souligner que ce n'est que pour les types de classe et pour le différencier des exemples 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!
}

Cela vous permet de faire des choses sympas comme utiliser un dictionnaire de références faibles:

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
la source
3

Voici comment faire @ grande réponse GoZoner conforme à Hashable, donc il peut être indexé dans les objets Conteneur comme: Set, Dictionary, Array, etc.

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

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

// Need to override so we can conform to Equitable.
private func == <T>(lhs: Weak<T>, rhs: Weak<T>) -> Bool {
    return lhs.hashValue == rhs.hashValue
}
Sakiboy
la source
3

Comme cela NSPointerArraygère déjà la plupart de cela automatiquement, j'ai résolu le problème en créant un emballage de type sécurisé, ce qui évite une grande partie du passe-partout dans d'autres réponses:

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
}

Exemple d'utilisation:

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

C'est plus de travail à l'avant, mais l'utilisation dans le reste de votre code est beaucoup plus propre à l'OMI. Si vous voulez le rendre plus semblable à un tableau, vous pouvez même implémenter un indice, en faire un SequenceType, etc. (mais mon projet n'en a besoin que appendet forEachje n'ai donc pas le code exact sous la main).

John Montgomery
la source
2

Encore une autre solution au même problème ... l'objectif de celui-ci est de stocker une référence faible à un objet, mais vous permet également de stocker une structure.

[Je ne sais pas à quel point il est utile, mais il a fallu un certain temps pour obtenir la bonne syntaxe]

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
la source
1

D'autres réponses ont couvert l'angle des génériques. Je pensais que je partagerais un code simple couvrant l' nilangle.

Je voulais un tableau statique (lu occasionnellement) de tous les Labels qui existent actuellement dans l'application, mais je ne voulais pas voir niloù se trouvaient les anciens.

Rien d'extraordinaire, c'est mon 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
la source
Qu'en est-il de l'utilisation flatMapau lieu de filter& map?
Lukas Kubanek
0

Je me suis basé sur le travail de @Eonil, car j'aimais la stratégie de fermeture de liaison faible, mais je ne voulais pas utiliser un opérateur de fonction pour une variable, car cela me semblait extrêmement contre-intuitif

Ce que j'ai fait, à la place, est comme suit:

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

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

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

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

De cette façon, vous pouvez faire quelque chose comme:

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
la source
0

C'est ma solution:

  • Nettoyer le tableau lorsqu'il est désalloué , car WeakObjectSet stocke et ne quitte pas WeakObject
  • Résoudre l'erreur fatale lorsqu'un élément dupliqué est trouvé dans l'ensemble

-

// 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
la source
0

Il s'agit d'une collection de type sécurisé qui contient des conteneurs d'objets faibles. Il supprime également automatiquement nil les conteneurs / wrappers lors de l'accès.

Exemple:

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 collection personnalisée 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
la source
0

Qu'en est-il d'une approche fonctionnelle ?

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

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

C'est l'idée principale, puis ajoutez une logique de commodité pour garder une trace de ce qui se trouve dans le tableau. Par exemple, on pourrait envisager la même approche avec un dictionnaire utilisant la clé pour trouver ce qui s'y trouve.

frouo
la source