web-dev-qa-db-fra.com

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

Je voudrais stocker un tableau de références faibles dans Swift. Le tableau lui-même ne doit pas être une référence faible, mais ses éléments. Je pense que Cocoa NSPointerArray en propose une version non-typée.

160
Bill

Créez un wrapper générique en tant que:

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

Lorsque vous définissez Weak, vous pouvez utiliser struct ou class.

En outre, pour vous aider à récolter le contenu d'un tableau, vous pouvez faire quelque chose comme:

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

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

135
GoZoner

C'est un peu tard pour faire la fête, mais essayez le mien. J'ai mis en œuvre en tant que Set pas 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 de Swift est Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

Le code peut être récupéré depuis Gist . https://Gist.github.com/codelynx/30d3c42a833321f17d39

** AJOUTÉ EN NOV.2017

J'ai mis à jour le code pour 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 indiqué par gokeji, j'ai compris que NSString ne serait pas désalloué en fonction du code d'utilisation ..__ 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 NSString par MyString comme ceci. Alors étrange de dire que ça marche.

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é une page étrange peut être liée à cette question.

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

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

Il dit que le problème est RESOLVED mais 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 vont au-delà de ce contexte, mais j'aimerais bien que quelqu'un le résolve.

10
Kaz Yoshikawa

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 bloque le compilateur Swift.

Voici une version d'un conteneur d'objets faibles 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]
10
rjkaplan

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 utilisant ceux-ci dans le tableau

var weakThings = WeakThing<Foo>[]()
9
Joshua Weinberg

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 WeakSet peut être initialisé avec n'importe quel type, et si ce type n'est pas conforme au protocole AnyObject, l'application plantera avec une raison détaillée. Mais je ne vois pas de meilleure solution pour le moment. 

La solution originale consistait à définir WeakSet de cette manière: 

class WeakSet<ObjectType: AnyObject>: SequenceType {}

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

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

Le code actuellement ci-dessus ne peut pas être compilé (Swift 2.1, Xcode 7.1).
C'est pourquoi j'ai abandonné la conformité à AnyObject et ajouté des gardes supplémentaires avec des assertions fatalError().

7
Vlad Papko

Que diriez-vous de wrapper 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)

Il suffit d'appeler la fermeture renvoyé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 les appels de fermeture.

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

L'exemple existant de WeakContainer est utile, mais il ne vous aide pas vraiment à utiliser des références faibles dans les conteneurs Swift existants tels que Listes et Dictionnaires.

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

Au cas où vous souhaiteriez utiliser le WeakContainer dans des dictionnaires, je l'ai également rendu hashable pour qu'il puisse être utilisé comme clé de dictionnaire.

J'ai également renommé WeakObject pour souligner qu'il s'agit uniquement de types de classe et pour le différencier des exemples de WeakContainer:

struct WeakObject<TYPE where TYPE:AnyObject> : Equatable, Hashable
{
    weak var _value : TYPE?
    let _originalHashValue : Int

    init (value: TYPE)
    {
        _value = value

        // We keep around the original hash value so that we can return it to represent this
        // object even if the value became Nil out from under us because the object went away.
        _originalHashValue = ObjectIdentifier(value).hashValue
    }

    var value : TYPE?
    {
        return _value
    }

    var hashValue: Int
    {
        return _originalHashValue
    }
}

func ==<T>(lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool
{
    if lhs.value == nil  &&  rhs.value == nil {
        return true
    }
    else if lhs.value == nil  ||  rhs.value == nil {
        return false
    }

    // If the objects are the same, then we are good to go
    return lhs.value! === rhs.value!
}

Cela vous permet de faire des choses intéressantes 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)
}
3
Tod Cunningham

Voici comment adapter l'excellente réponse de @ GoZoner à Hashable afin qu'elle puisse être indexée dans des objets conteneur tels que: 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
}
2
Sakiboy

Basé sur Kaz Yoshikawa réponse

Détails

xCode 9.1, Swift 4

Solution

WeakObject

import Foundation

protocol WeakObjectProtocol {
    associatedtype WeakObjectType
    var value: WeakObjectType? {get set}
    init(object: WeakObjectType)
}

class WeakObject<T: AnyObject>: WeakObjectProtocol {
    typealias WeakObjectType = T
    weak var value: WeakObjectType?

    required init(object: WeakObjectType) {
        self.value = object
    }

    var referenceCount: Int {
        return CFGetRetainCount(value)
    }
}

extension WeakObject: Equatable {
    static func == (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
        return lhs.value === rhs.value
    }
}

extension WeakObject: Hashable {
    var hashValue: Int {
        if var value = value { return UnsafeMutablePointer<T>(&value).hashValue }
        return 0
    }
}

extension WeakObject: CustomStringConvertible {

    var description: String {
        if let value = value  {
            let className = String(describing: type(of: value.self))
            return "{class: \(className); referenceCount: \(referenceCount)}"
        }
        return "nil"
    }
}

extension tableau

import Foundation

extension Array where Element: AnyObject  {

    var weak: Array<WeakObject<Element>> {
        var weakArray = [WeakObject<Element>]()
        for item in self {
            let obj = WeakObject(object: item)
            weakArray.append(obj)
        }
        return weakArray
    }
}

extension Array where Element: WeakObjectProtocol {

    typealias EnumeratedWeakObjectClosure = (_ index: Int, _ value: Element.WeakObjectType?)->()
    typealias WeakObjectClosure = (_ value: Element.WeakObjectType?)->()

    mutating func removeNils() {
        self = self.flatMap{ (element) -> Element? in
            if element.value == nil {
                return nil
            }
            return element
        }
    }

    mutating func append(weakValue: Element.WeakObjectType) {
        append(Element(object: weakValue))
    }

    subscript(index: Int) -> Element.WeakObjectType? {
        get {
            return self[index].value
        }
    }

    func `for` (closure: WeakObjectClosure){
        for item in self {
            closure(item.value)
        }
    }

    func forEnumerated (closure: EnumeratedWeakObjectClosure) {
        for (index,item) in self.enumerated() {
            closure(index, item.value)
        }
    }

    mutating func remove(index: Int, closure: EnumeratedWeakObjectClosure) {
        closure(index, self[index].value)
        remove(at: index)
    }

    mutating func remove(index: Int, closure: WeakObjectClosure) {
        closure(self[index].value)
        remove(at: index)
    }
}

Usage

// Array of week objects
var weakArray = [WeakObject<UIView>]()

// Get array of week objects (transfom from [AnyObject])
// way 1
weakArray = view.subviews.weak
// way 2
weakArray = [view.subviews[0], view.subviews[1]].weak

// Add single element to the end of the array
weakArray.append(weakValue: UIView())

// For loop
weakArray.for { (element) in
    print("\(String(describing: element))")
}

// For loop with index (position number)
weakArray.forEnumerated { (index, element) in
    print("\(index) \(String(describing: element))")
}

Échantillon complet

N'oubliez pas de ajouter le code de solution ici


ViewController

import UIKit

class ViewController: UIViewController {

    var weakArray = [WeakObject<UIView>]()
    override func viewDidLoad() {
        super.viewDidLoad()
        addSubviews()

        weakArray = view.subviews.weak
        weakArray.append(weakValue: generateView())
        weakArray.remove(index: 0) { item in
            item?.removeFromSuperview()
        }
        weakArray.for { (element) in
            print("\(String(describing: element))")
        }

    }

    func printArray(title: String) {

        print("=============================\n\(title)\ncount: \(weakArray.count)")
        weakArray.forEnumerated { (index,element) in
            print("\(index) \(String(describing: element))")
        }
    }
}

// Creating views

extension ViewController {

    func generateView() -> UIView {
        let randomValue: ()->(CGFloat) = { return CGFloat(Rand[50, 300]) }
        let view = UIView(frame: CGRect(x: randomValue(), y: randomValue(), width: randomValue(), height: randomValue()))
        view.backgroundColor = .blue
        let randomColorComponent: ()->(CGFloat) = { return CGFloat(Rand[0, 255])/CGFloat(255) }
        let color = UIColor(red: randomColorComponent(), green: randomColorComponent(), blue: randomColorComponent(), alpha: 1)
        view.backgroundColor = color
        self.view.addSubview(view)
        return view
    }

    func addSubviews() {

        _ = generateView()
        _ = generateView()

        addButtons()
    }
}

// Buttons

extension ViewController {

    func addButtons() {
        var button = UIButton(frame: CGRect(x: 10, y: 20, width: 40, height: 40))
        button.setTitle("Add", for: .normal)
        button.addTarget(self, action: #selector(addView), for: .touchUpInside)
        button.setTitleColor(.blue, for: .normal)
        view.addSubview(button)

        button = UIButton(frame: CGRect(x: 60, y: 20, width: 60, height: 40))
        button.setTitle("Delete", for: .normal)
        button.addTarget(self, action: #selector(deleteView), for: .touchUpInside)
        button.setTitleColor(.blue, for: .normal)
        view.addSubview(button)

        button = UIButton(frame: CGRect(x: 120, y: 20, width: 100, height: 40))
        button.setTitle("Remove nil", for: .normal)
        button.addTarget(self, action: #selector(removeNils), for: .touchUpInside)
        button.setTitleColor(.blue, for: .normal)
        view.addSubview(button)
    }

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

        DispatchQueue.main.async {
            self.view.layoutIfNeeded()
            self.printArray(title: "First view deleted")
        }
    }

    @objc func addView() {
        weakArray.append(weakValue: generateView())
        printArray(title: "View addded")
    }

    @objc func removeNils() {
        weakArray.removeNils()
        printArray(title: "Remove all nil elements in weakArray")
    }
}

Rand func

class Random {

    subscript<T>(_ min: T, _ max: T) -> T where T : BinaryInteger {
        get {
            return Rand(min-1, max+1)
        }
    }
}

let Rand = Random()

func Rand<T>(_ min: T, _ max: T) -> T where T : BinaryInteger {
    let _min = min + 1
    let difference = max - _min
    return T(arc4random_uniform(UInt32(difference))) + _min
}

Résultat

 enter image description here

2
Vasily Bodnarchuk

Encore une autre solution au même problème ... l’accent est mis sur le stockage d’une référence faible à un objet, mais vous permet également de stocker une structure.

[Je ne suis pas sûr de son utilité, mais il a fallu un certain temps pour que la syntaxe soit correcte]

class WeakWrapper : Equatable {
    var valueAny : Any?
    weak var value : AnyObject?

    init(value: Any) {
        if let valueObj = value as? AnyObject {
            self.value = valueObj
        } else {
            self.valueAny = value
        }
    }

    func recall() -> Any? {
        if let value = value {
            return value
        } else if let value = valueAny {
            return value
        }
        return nil
    }
}


func ==(lhs: WeakWrapper, rhs: WeakWrapper) -> Bool {
    return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}



class Stuff {}
var weakArray : [WeakWrapper] = [WeakWrapper(value: Stuff()), WeakWrapper(value: CGRectZero)]

extension Array where Element : WeakWrapper  {

    mutating func removeObject(object: Element) {
        if let index = self.indexOf(object) {
            self.removeAtIndex(index)
        }
    }

    mutating func compress() {
        for obj in self {
            if obj.recall() == nil {
                self.removeObject(obj)
            }
        }
    }


}

weakArray[0].recall()
weakArray[1].recall() == nil
weakArray.compress()
weakArray.count
1
Dan Rosenstark

Étant donné que NSPointerArray gère déjà la plupart de ces problèmes automatiquement, j'ai résolu le problème en créant un wrapper de type sans danger pour le clavier, ce qui évite beaucoup de problèmes dans les réponses suivantes:

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

Cela demande plus de travail à l’avance, mais l’utilisation dans le reste de votre code est beaucoup plus propre, OMI. Si vous voulez le rendre plus semblable à un tableau, vous pouvez même implémenter des indices, en faire un SequenceType, etc. (mais mon projet n'a besoin que de append et forEach, je n'ai donc pas le code exact sous la main).

1
John Montgomery

D'autres réponses ont couvert l'angle des génériques. Je pensais partager un code simple couvrant l’angle nil.

Je voulais un tableau statique (à lire occasionnellement) de tous les Labels qui existent actuellement dans l'application, mais je ne voulais pas voir nil où étaient 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))
    }
}
1
wils

Vous pouvez créer un wrapper autour de Array. Ou utilisez cette bibliothèque https://github.com/NickRybalko/WeakPointerArray .____ let array = WeakPointerArray<AnyObject>() It est en sécurité.

0
Nick Rybalko

Je me suis basé sur le travail de @Eonil, car j’ai adoré la stratégie de fermeture faible liaison, mais je n’ai pas voulu utiliser un opérateur de fonction pour une variable, car cela me semblait extrêmement contre

Ce que j'ai fait, à la place, est le suivant:

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

Il s'agit d'une collection de type sécurisée qui contient des conteneurs d'objets faibles. Il supprime également automatiquement les conteneurs/wrappers lorsqu’il est accédé.

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

C'est ma solution:

  • Nettoyer le tableau après désallocation , car WeakObjectSet est en cours de stockage et ne parvient pas à se libérer
  • Résolvez l'erreur fatale lorsque un élément en double trouvé dans Set

-

// MARK: - WeakObjectSet 

public class WeakObject<T: AnyObject>: Equatable, Hashable {

    // MARK: Public propreties

    public weak var object: T?
    public var hashValue: Int {
        return self.identifier.hashValue
    }

    // MARK: Private propreties

    private let identifier: ObjectIdentifier

    // MARK: Initializer

    public init(object: T) {
        self.identifier = ObjectIdentifier(object)
        self.object = object
    }

    public static func == (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
        return lhs.identifier == rhs.identifier
    }
}

// MARK: - WeakObjectSet

public class WeakObjectSet<T: AnyObject> {

    // MARK: Public propreties

    public var allObjects: [T] {
        return allSetObjects.compactMap { $0.object }
    }

    // MARK: Private propreties

    private var objects: Set<WeakObject<T>>
    private var allSetObjects: Set<WeakObject<T>> {
        get {
            objects = self.objects.filter { $0.object != nil }
            return objects
        }
        set {
            objects.formUnion(newValue.filter { $0.object != nil })
        }
    }

    // MARK: Initializer

    public init() {
        self.objects = Set<WeakObject<T>>([])
    }

    public init(objects: [T]) {
        self.objects = Set<WeakObject<T>>(objects.map { WeakObject(object: $0) })
    }

    // MARK: Public function

    public func contains(_ object: T) -> Bool {
        return self.allSetObjects.contains(WeakObject(object: object))
    }

    public func addObject(_ object: T) {
        self.allSetObjects.insert(WeakObject(object: object))
    }

    public func addObjects(_ objects: [T]) {
        objects.forEach { self.allSetObjects.insert(WeakObject(object: $0)) }
    }
}
0
YannickSteph