Comment puis-je 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 devrait pas être une référence faible - ses éléments doivent être. Je pense que Cocoa NSPointerArray
offre une version non-typesafe de ceci.
15 réponses
Créer 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 de Weak
, vous pouvez utiliser soit struct
ou class
.
Aussi, 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 AnyObject
ci - dessus devrait être remplacée par T
- mais je ne pense pas que le langage Swift actuel permette une extension définie comme telle.
Vous pouvez utiliser NSHashTable avec weakObjectsHashTable. NSHashTable.weakObjectsHashTable()
Pour Swift 3: NSHashTable.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.
Ce n'est pas ma solution. Je l'ai trouvé sur les Forums de développeurs Apple .
@GoZoner a une bonne réponse, mais il bloque le compilateur Swift.
Voici une version d'un conteneur d'objets faibles qui ne plante pas le compilateur publié en cours.
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]
Il est un peu tard pour la fête, mais essayez le mien. J'ai implémenté comme un ensemble 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) })
}
}
Utilisation
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]
Méfiez-vous que WeakObjectSet ne prendra pas le type de chaîne mais NSString. Parce que, le type de chaîne n'est pas un AnyType. Ma version swift est Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)
.
Le Code peut être saisi à partir de Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39
* * AJOUTÉ EN NOV.2017
J'ai mis à jour le code à 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 a mentionné, j'ai compris que NSString ne sera pas désalloué en fonction du code utilisé. Je me suis gratté la tête et j'ai écrit MyString classe 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
avec MyString
comme ceci. Alors é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é une page étrange peut être liée à ce problème.
La référence faible conserve NSString désalloué (xC9 + iOS Sim uniquement)
Https://bugs.swift.org/browse/SR-5511
Il dit le 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 sont au-delà de ce contexte, mais j'apprécierais si quelqu'un a compris ce problème.
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
}
}
Puis en les utilisant dans le tableau
var weakThings = WeakThing<Foo>[]()
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éé 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?
}
}
}
Utilisation:
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 se bloque avec une raison détaillée. Mais je ne vois pas de meilleure solution en ce moment.
Solution Originale était de définir WeakSet
de cette façon:
class WeakSet<ObjectType: AnyObject>: SequenceType {}
, Mais dans ce cas - WeakSet
impossible d'initialiser 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é me conformer à AnyObject
et ajouté des gardes supplémentaires avec des assertions fatalError()
.
Que diriez-vous d'un 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)
Appelez simplement la fermeture retournée pour vérifier que la cible est toujours vivante.
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() }))
L'exemple existant de WeakContainer est utile, mais il n'aide pas vraiment à utiliser des références faibles dans des conteneurs swift existants tels que des listes et des 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 souhaiteriez utiliser le WeakContainer dans les dictionnaires, Je l'ai également rendu hashable afin qu'il puisse être utilisé comme dictionnaire touches.
Je l'ai également renommé en 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 trucs cool 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)
}
Voici comment rendre la grande réponse de @ GoZoner conforme à Hashable
, de sorte qu'elle peut être indexée dans des 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
}
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"
}
}
Tableau D'Extension
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)
}
}
Utilisation
// 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 la 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")
}
}
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
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 (lu occasionnellement) de tous les Label
qui existent actuellement dans l'application, mais je ne voulais pas voir nil
où 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))
}
}
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 permettant de stocker une structure aussi.
[Je ne suis pas sûr de son utilité, 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
Vous pouvez créer un wrapper autour de Array
. Ou utilisez cette bibliothèque https://github.com/NickRybalko/WeakPointerArray
let array = WeakPointerArray<AnyObject>()
Il est de type sûr.
J'ai basé cela sur le travail de @Eonil, puisque j'aimais la stratégie de fermeture à 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
Puisque NSPointerArray
gère déjà la plupart de cela automatiquement, j'ai résolu le problème en faisant un wrapper de type-safe pour cela, ce qui évite beaucoup de 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 IMO. Si vous voulez le rendre plus semblable à un tableau, vous pouvez même implémenter l'subscripting, en faire un SequenceType
, etc. (mais mon projet n'a besoin que de append
et forEach
donc je n'ai pas le code exact sur main).