variable print basics objective-c automatic-ref-counting swift weak-references nspointerarray

objective c - print - ¿Cómo declaro una matriz de referencias débiles en Swift?



swift<- (13)

Me gustaría almacenar una serie de referencias débiles en Swift. La matriz en sí no debería ser una referencia débil; sus elementos deberían serlo. Creo que Cocoa NSPointerArray ofrece una versión no segura de esto.


Basado en la respuesta de Kaz Yoshikawa

Detalles

xCode 9.1, Swift 4

Solución

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

extensión Array

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

Uso

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

Muestra completa

No olvides agregar el código de la solución aquí

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 }

Resultado


¿Qué tal una envoltura 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)

Solo llame al cierre devuelto para verificar que el objetivo aún esté activo.

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

Y puedes almacenar estos cierres en una matriz.

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

Y puede recuperar los valores capturados débilmente haciendo un mapeo llamando a los cierres.

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


Aquí se explica cómo hacer que la gran respuesta de Hashable ajuste a Hashable , por lo que puede indexarse ​​en objetos Container 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 }


Crea un contenedor genérico como:

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

Agregue instancias de esta clase a su matriz.

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

Al definir Weak puedes usar cualquier struct o class .

Además, para ayudar con la recolección de contenidos de matriz, podría hacer algo como:

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

El uso de AnyObject anterior debería reemplazarse por T , pero no creo que el lenguaje Swift actual permita una extensión definida como tal.


El ejemplo existente de WeakContainer es útil, pero realmente no ayuda a utilizar referencias débiles en contenedores rápidos existentes como listas y diccionarios.

Si desea utilizar métodos de lista como, por ejemplo, WeakContainer necesitará implementar Equatable. Así que agregué el código para permitir que el WeakContainer sea igual.

En caso de que quisiera usar el WeakContainer en los diccionarios, también lo hice manejable para que pueda usarse como claves del diccionario.

También lo renombré a WeakObject para enfatizar que esto es solo para tipos de clase y para diferenciarlo de los ejemplos 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! }

Esto le permite hacer algunas cosas interesantes como usar un diccionario de referencias débiles:

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


Es tarde para la fiesta, pero prueba la mía. Implementé como un conjunto, no como una 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]

Tenga en cuenta que WeakObjectSet no tomará String type sino NSString. Porque, String type no es un AnyType. Mi versión rápida es Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29) .

El código puede ser tomado de Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** AGREGADO EN NOV.2017

Actualicé el código a 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 mencionó Gokeji, me di cuenta de que NSString no será desasignado en función del código en uso. Me rasqué la cabeza y escribí clase MyString de la siguiente manera.

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

A continuación, reemplace NSString con MyString esta manera. Entonces, es extraño decir 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]

Luego encontré una página extraña que puede estar relacionada con este problema.

La referencia débil retiene NSString desasignado (solo XC9 + iOS Sim)

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

Dice que el problema se RESOLVED pero me pregunto si esto todavía está relacionado con este problema. De todos modos, las diferencias de comportamiento entre MyString o NSString están más allá de este contexto, pero agradecería que alguien resolviera este problema.


Otra solución para el mismo problema ... el objetivo de esto es almacenar una referencia débil a un objeto pero permitiéndole también almacenar una estructura.

[No estoy seguro de lo útil que es, pero tomó un tiempo para obtener la sintaxis correcta]

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


Otras respuestas han cubierto el ángulo de los genéricos. Pensé que compartiría un código simple que cubre el ángulo nil .

Quería una matriz estática (leer ocasionalmente) de todas las Label que actualmente existen en la aplicación, pero no quería ver las nil donde estaban las anteriores.

Nada elegante, este es mi 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)) } }


Puede hacerlo creando un objeto contenedor para mantener un puntero débil.

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

Y luego usarlos en la matriz

var weakThings = WeakThing<Foo>[]()


Puede usar NSHashTable con weakObjectsHashTable. NSHashTable.weakObjectsHashTable()

Para Swift 3: NSHashTable.weakObjects()

Referencia de la clase NSHashTable

Disponible en OS X v10.5 y posterior.

Disponible en iOS 6.0 y posterior.



Tuve la misma idea de crear contenedores débiles con genéricos.
Como resultado, creé el contenedor 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() }

No es la mejor solución, porque WeakSet se puede inicializar con cualquier tipo, y si este tipo no se ajusta al protocolo AnyObject , la aplicación se bloqueará con un motivo detallado. Pero no veo ninguna solución mejor en este momento.

La solución original fue definir WeakSet de esta manera:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

Pero en este caso, WeakSet no se puede inicializar con el protocolo:

protocol MyDelegate : AnyObject { func doWork() } let weakSet = WeakSet<MyDelegate>()

Actualmente no se puede compilar el código anterior (Swift 2.1, Xcode 7.1).
Es por eso que dejé de conformarme a AnyObject y agregué guardias adicionales con las afirmaciones fatalError() .


Esta no es mi solución. Lo encontré en los foros de desarrolladores de Apple .

@GoZoner tiene una buena respuesta, pero bloquea el compilador Swift.

Aquí hay una versión de un contenedor de objetos débiles que no bloquea el compilador publicado actualmente.

struct WeakContainer<T where T: AnyObject> { weak var _value : T? init (value: T) { _value = value } func get() -> T? { return _value } }

A continuación, puede crear una matriz de estos contenedores:

let myArray: Array<WeakContainer<MyClass>> = [myObject1, myObject2]