Como declaro uma matriz de referências fracas no Swift?

179

Eu gostaria de armazenar uma série de referências fracas no Swift. A matriz em si não deve ser uma referência fraca - seus elementos devem ser. Eu acho que o Cocoa NSPointerArrayoferece uma versão não tipesafe disso.

Conta
fonte
1
Que tal criar um objeto contêiner que faça referência fraca a outro objeto e criar uma matriz desses? (Se você não obter uma resposta melhor)
nielsbot
1
por que você não usa um NSPointerArray?
Bastian
@nielsbot Essa é uma solução obj-c antiga :) Para torná-lo Swifty, deve ser um objeto genérico! :) No entanto, o verdadeiro problema é como remover objetos da matriz quando o objeto referenciado é desalocado.
Sulthan
2
Certo, eu preferiria algo com tipos parametrizados. Eu acho que eu poderia fazer um wrapper parametrizado em torno do NSPointerArray, mas queria ver se havia alguma alternativa.
Bill
6
Assim como outra opção, o NSHashTable existe. É basicamente um NSSet que permite especificar como deve fazer referência aos objetos que ele contém.
Mick MacCallum

Respostas:

154

Crie um wrapper genérico como:

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

Adicione instâncias dessa classe à sua matriz.

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

Ao definir, Weakvocê pode usar structou class.

Além disso, para ajudar a colher o conteúdo da matriz, você pode fazer algo como:

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

O uso AnyObjectacima deve ser substituído por T- mas não acho que a linguagem Swift atual permita uma extensão definida como tal.

GoZoner
fonte
11
Como você remove os objetos wrapper da matriz quando seu valor é desalocado?
Sulthan
9
Sim, travou o compilador.
GoZoner
5
Poste seu código do problema em uma nova pergunta; nenhuma razão para ding minha resposta quando ele pode ser o seu código!
GoZoner 30/07/2015
2
@EdGamble O código fornecido funciona como está, mas falha se você substituir a classe Stuffpor um protocolo; veja esta questão relacionada
Theo
2
Uma estrutura seria melhor, pois seria mantida na pilha em vez de precisar de uma busca de heap.
KPM
60

Você pode usar o NSHashTable com o fracoObjectsHashTable. NSHashTable<ObjectType>.weakObjectsHashTable()

Para o Swift 3: NSHashTable<ObjectType>.weakObjects()

Referência da classe NSHashTable

Disponível no OS X v10.5 e posterior.

Disponível no iOS 6.0 e posterior.

Thierry
fonte
Melhor resposta e não tempo de cintura para embalagens!
Ramis #
1
Isso é inteligente, mas, como a resposta do GoZoner, isso não funciona com tipos que são, Anymas não AnyObject, como protocolos.
Aaron Brager
@SteveWilford Mas um protocolo pode ser implementado por uma classe, o que tornaria um tipo de referência
Aaron Brager
4
um protocolo pode estender classe e, em seguida, pode usá-lo como fraco (por exemplo MyProtocol protocolo: classe)
Yasmin Tiomkin
1
Eu recebo um erro do compilador com MyProtocol: classe NSHashTable<MyProtocol>.weakObjects(). "'NSHashTable' requer que 'MyProtocol' seja um tipo de classe.
Greg
14

É meio tarde para a festa, mas experimente o meu. Eu implementei como um conjunto, não uma matriz.

WeakObjectSet

class WeakObject<T: AnyObject>: Equatable, Hashable {
    weak var object: T?
    init(object: T) {
        self.object = object
    }

    var hashValue: Int {
        if let object = self.object { return unsafeAddressOf(object).hashValue }
        else { return 0 }
    }
}

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


class WeakObjectSet<T: AnyObject> {
    var objects: Set<WeakObject<T>>

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

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

    var allObjects: [T] {
        return objects.flatMap { $0.object }
    }

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

    func addObject(object: T) {
        self.objects.unionInPlace([WeakObject(object: object)])
    }

    func addObjects(objects: [T]) {
        self.objects.unionInPlace(objects.map { WeakObject(object: $0) })
    }
}

Uso

var alice: NSString? = "Alice"
var bob: NSString? = "Bob"
var cathline: NSString? = "Cathline"

var persons = WeakObjectSet<NSString>()
persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObjects([alice!, cathline!])
print(persons.allObjects) // [Alice, Cathline, Bob]

alice = nil
print(persons.allObjects) // [Cathline, Bob]

bob = nil
print(persons.allObjects) // [Cathline]

Cuidado que WeakObjectSet não aceita o tipo String, mas NSString. Porque, o tipo String não é um AnyType. Minha versão rápida é Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

O código pode ser obtido no Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** ADICIONADO EM NOV.2017

Atualizei o código para o 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) })
    }
}

Como gokeji mencionou, eu descobri que o NSString não será desalocado com base no código em uso. Cocei minha cabeça e escrevi a classe MyString da seguinte maneira.

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

Em seguida, substitua NSStringpor MyStringassim. Então estranho dizer que funciona.

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]

Então, descobri que uma página estranha pode estar relacionada a esse problema.

A referência fraca mantém o NSString desalocado (apenas XC9 + iOS Sim)

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

Ele diz que o problema é, RESOLVEDmas gostaria de saber se isso ainda está relacionado a esse problema. De qualquer forma, as diferenças de comportamento entre MyString ou NSString estão além desse contexto, mas eu apreciaria se alguém descobrisse esse problema.

Kaz Yoshikawa
fonte
Adotei esta solução para o meu projeto. Bom trabalho! Apenas uma sugestão, esta solução não parece remover nilvalores do interno Set. Então, adicionei uma reap()função mencionada na resposta superior e lembrei-me de ligar reap()sempre que o WeakObjectSetacesso for feito.
gokeji
Hmm espera, por algum motivo isso não funciona na Swift 4 / iOS 11. Parece que a referência fraca não se desalocadas imediatamente quando o valor torna-se nilmais
gokeji
1
Atualizei o código para o Swift4, veja a segunda metade da resposta. Parece que o NSString tem alguns problemas de desalocação, mas ainda deve funcionar em seus objetos de classe personalizados.
Kaz Yoshikawa
Muito obrigado por olhar para ele @KazYoshikawa e atualizar a resposta! Também percebi depois que uma classe personalizada funciona, enquanto NSStringnão funciona.
Gokeji
2
Fiz a experiência de que o ponteiro retornado UnsafeMutablePointer<T>(&object)pode mudar aleatoriamente (o mesmo com withUnsafePointer). Agora uso uma versão suportada por NSHashTable: gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d .
2141818 simonseyer
12

Esta não é a minha solução. Encontrei-o nos Fóruns de desenvolvedores da Apple .

O @GoZoner tem uma boa resposta, mas trava o compilador Swift.

A seguir, uma versão de um contêiner de objeto fraco não trava o compilador lançado atualmente.

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

    init (value: T) {
        _value = value
    }

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

Você pode criar uma matriz desses contêineres:

let myArray: Array<WeakContainer<MyClass>> = [myObject1, myObject2]
rjkaplan
fonte
1
estranho, mas não funciona mais com estruturas. Diz EXC_BAD_ACCESSpara mim. Com classe funciona muito bem
mente
6
Estruturas são tipos de valor, não devem funcionar com elas. O fato de ter travado no tempo de execução em vez de ser um erro em tempo de compilação é um bug do compilador.
David Goodine
10

Você pode fazer isso criando um objeto wrapper para manter um ponteiro fraco.

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

E depois usá-los na matriz

var weakThings = WeakThing<Foo>[]()
Joshua Weinberg
fonte
Tem que ser um classpara usar weakvars #
Bill
3
Quem disse? O código acima funciona bem para mim. A única exigência é que o objeto se tornar necessidades fracos para ser uma classe, não o objeto mantendo a referência fraca
Joshua Weinberg
Desculpe. Eu poderia jurar que acabei de receber uma mensagem do compilador que dizia "Não é possível usar variáveis ​​fracas em estruturas". Você está correto - isso compila.
Bill
5
@JoshuaWeinberg e se o Foo for um protocolo?
precisa saber é o seguinte
@ onmyway133 AFAIK se o protocolo for declarado implementado apenas por classes, ele funcionaria. protocol Protocol : class { ... }
Olajnjak
8

E o wrapper de estilo funcional?

class Class1 {}

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

let obj1 = Class1()
let obj2 = Class1()
let obj3 = Class1()
let captured1 = captureWeakly(obj1)
let captured2 = captureWeakly(obj2)
let captured3 = captureWeakly(obj3)

Basta ligar para o fechamento retornado para verificar se o alvo ainda está vivo.

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

E você pode armazenar esses fechamentos em uma matriz.

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

E você pode recuperar os valores fracamente capturados mapeando os fechamentos.

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

Na verdade, você não precisa de uma função para fazer um fechamento. Basta capturar um objeto diretamente.

let captured3 = { [weak obj3] in return obj3 }
eonil
fonte
3
A questão é como criar uma matriz (ou dizer Conjunto) de objetos fracos.
David H
Com esta solução, você pode até criar uma matriz com vários valores, como var array: [(x: Int, y: () -> T?)]. Exatamente o que eu estava procurando.
jboi
1
@ Davidid Atualizei minha resposta para responder à pergunta. Eu espero que isso ajude.
eonil
Eu amei esta abordagem, e eu acho que é super inteligente. Fiz uma implementação de classe usando essa estratégia. Obrigado!
Ale Ravasio 16/08
Não tenho certeza sobre o let values = Array(array1.map({ $0() })) part. Como não é mais uma matriz de fechamentos com referências fracas, os valores serão retidos até que essa matriz seja desalocada. Se eu estiver correto, é importante observar que você nunca deve reter essa matriz, self.items = Array(array1.map({ $0() }))pois isso supera o objetivo.
Matic Oblak
7

Eu tive a mesma idéia de criar contêineres fracos com genéricos.
Como resultado, criei o wrapper para NSHashTable:

class WeakSet<ObjectType>: SequenceType {

    var count: Int {
        return weakStorage.count
    }

    private let weakStorage = NSHashTable.weakObjectsHashTable()

    func addObject(object: ObjectType) {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        weakStorage.addObject(object as? AnyObject)
    }

    func removeObject(object: ObjectType) {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        weakStorage.removeObject(object as? AnyObject)
    }

    func removeAllObjects() {
        weakStorage.removeAllObjects()
    }

    func containsObject(object: ObjectType) -> Bool {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        return weakStorage.containsObject(object as? AnyObject)
    }

    func generate() -> AnyGenerator<ObjectType> {
        let enumerator = weakStorage.objectEnumerator()
        return anyGenerator {
            return enumerator.nextObject() as! ObjectType?
        }
    }
}

Uso:

protocol MyDelegate : AnyObject {
    func doWork()
}

class MyClass: AnyObject, MyDelegate {
    fun doWork() {
        // Do delegated work.
    }
}

var delegates = WeakSet<MyDelegate>()
delegates.addObject(MyClass())

for delegate in delegates {
    delegate.doWork()
}

Não é a melhor solução, porque WeakSet pode ser inicializada com qualquer tipo e, se esse tipo não estiver em conformidade comAnyObject protocolo, o aplicativo falhará por motivos detalhados. Mas não vejo melhor solução no momento.

A solução original foi definir WeakSet desta maneira:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

Mas neste caso WeakSet não pode ser inicializado com o protocolo:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

Atualmente, o código acima não pode ser compilado (Swift 2.1, Xcode 7.1).
Foi por isso que deixei de cumprir AnyObjecte adicionei guardas adicionais com fatalError()afirmações.

Vlad Papko
fonte
Huh usar apenas para o objeto em hashtable.allObjects
malhal
6

Detalhes

  • Swift 5.1, Xcode 11.3.1

Solução

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

Opção 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) }
    }
}

Uso da opção 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) }
    }
}

opção 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) })
    }
}

Uso da opção 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) }
    }
}

Amostra completa

não se esqueça de colar o código da solução

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
fonte
Meu problema com as duas opções (e muitas outras) é que esses tipos de matriz não são utilizáveis ​​com protocolos. Por exemplo, isso não será compilado:protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
Matic Oblak
@MaticOblak, que tal usar genéricos? 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
A idéia é que essa matriz possa conter objetos de diferentes tipos que implementam o mesmo protocolo de classe. Usando um genérico, você o bloqueia em um único tipo. Por exemplo, imagine ter um singleton que contém uma matriz como delegates. Então você teria um número de controladores de exibição que gostariam de usar essa funcionalidade. Você esperaria ligar MyManager.delegates.append(self). Mas se MyManagerestiver bloqueado para algum tipo genérico, isso não será muito útil.
Matic Oblak
@MaticOblak ok. Tente isto: 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
Agora você perdeu a parte genérica com a matriz, o que é um pouco importante :) Tenho a sensação de que isso não é possível. Uma limitação do Swift por enquanto ...
Matic Oblak 16/06
4

O exemplo existente do WeakContainer é útil, mas realmente não ajuda a usar referências fracas em contêineres rápidos existentes, como Listas e Dicionários.

Se você quiser usar os métodos List, como o contains, o WeakContainer precisará implementar o Equatable. Então, adicionei o código para permitir que o WeakContainer seja equacionável.

No caso de você querer usar o WeakContainer nos dicionários, também o tornei lavável para que possa ser usado como chaves de dicionário.

Também o renomei para WeakObject para enfatizar que isso é apenas para tipos de classe e para diferenciá-lo dos exemplos 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!
}

Isso permite que você faça coisas legais, como usar um dicionário de referências fracas:

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

Aqui está como fazer @ grande resposta do GoZoner em conformidade com Hashable, por isso pode ser indexado no contentor de objectos como: 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
fonte
3

Como NSPointerArrayjá lida com a maior parte disso automaticamente, resolvi o problema criando um invólucro com segurança de tipo para ele, o que evita muito do clichê nas outras respostas:

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
}

Exemplo de uso:

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

É mais trabalhoso, mas o uso no restante do seu código é IMO muito mais limpo. Se você quiser torná-lo mais parecido com um array, pode até implementar subscrição, torná-lo um SequenceTypeetc., (mas meu projeto só precisa appende, forEachportanto, não tenho o código exato em mãos).

John Montgomery
fonte
2

Ainda outra solução para o mesmo problema ... o foco deste é armazenar uma referência fraca a um objeto, mas permitir que você armazene uma estrutura também.

[Não tenho certeza de quão útil é, mas demorou um pouco para acertar a sintaxe]

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

Outras respostas cobriram o ângulo dos genéricos. Pensei em compartilhar um código simples cobrindo o nilângulo.

Eu queria uma matriz estática (leia ocasionalmente) de todos os Labels existentes no aplicativo, mas não queria vernil onde estavam os antigos.

Nada extravagante, este é o meu código ...

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
fonte
Que tal usar em flatMapvez de filter& map?
Lukas Kubanek
0

Baseei isso no trabalho do @Eonil, pois adorei a estratégia de fechamento fraco, mas não queria usar um operador de função para uma variável, pois parecia extremamente contra-intuitivo

O que eu fiz, em vez disso, é o seguinte:

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

Dessa forma, você pode fazer algo como:

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

Esta é a minha solução:

  • Limpa a matriz quando desalocada , porque o WeakObjectSet está armazenando e não saindo do WeakObject
  • Resolva o erro fatal quando um elemento duplicado for encontrado em 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)) }
    }
}
YannSteph
fonte
0

Essa é uma coleção segura do tipo que contém contêineres de objetos fracos. Também remove automaticamente os contêineres / invólucros quando é acessado.

Exemplo:

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

A coleção personalizada 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
fonte
0

Que tal uma abordagem funcional ?

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

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

Essa é a idéia principal e, em seguida, adicione qualquer lógica de conveniência para acompanhar o que está na matriz. Por exemplo, pode-se considerar a mesma abordagem com um Dicionário usando key como uma maneira de encontrar o que está lá.

frouo
fonte