How do I declare an array of weak references in Sw

2019-01-07 03:39发布

问题:

I'd like to store an array of weak references in Swift. The array itself should not be a weak reference - its elements should be. I think Cocoa NSPointerArray offers a non-typesafe version of this.

回答1:

Create a generic wrapper as:

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

Add instances of this class to your array.

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

When defining Weak you can use either struct or class.

Also, to help with reaping array contents, you could do something along the lines of:

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

The use of AnyObject above should be replaced with T - but I don't think the current Swift language allows an extension defined as such.



回答2:

You can use the NSHashTable with weakObjectsHashTable. NSHashTable.weakObjectsHashTable()

For Swift 3: NSHashTable.weakObjects()

NSHashTable Class Reference

Available in OS X v10.5 and later.

Available in iOS 6.0 and later.



回答3:

This is not my solution. I found it on the Apple Developer Forums.

@GoZoner has a good answer, but it crashes the Swift compiler.

Here's a version of a weak-object container doesn't crash the current released compiler.

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

    init (value: T) {
        _value = value
    }

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

You can then create an array of these containers:

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


回答4:

It's kind of late for party, but try mine. I implemented as a Set not an Array.

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]

Beware that WeakObjectSet won't take String type but NSString. Because, String type is not an AnyType. My swift version is Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

Code can be grabbed from Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** ADDED IN NOV.2017

I updated the code to 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) })
    }
}

As gokeji mentioned, I figured out NSString won't get deallocated based on the code in usage. I scratched my head and I wrote MyString class as follows.

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

Then replace NSString with MyString like this. Then strange to say it works.

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]

Then I found a strange page may be related to this issue.

Weak reference retains deallocated NSString (XC9 + iOS Sim only)

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

It says the issue is RESOLVED but I am wondering if this is still related to this issue. Anyway, Behavior differences between MyString or NSString are beyond this context, but I would appreciate if someone figured this issue out.



回答5:

You can do this by creating a wrapper object to hold a weak pointer.

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

And then using these in the array

var weakThings = WeakThing<Foo>[]()


回答6:

I had the same idea to create weak container with generics.
As result I created wrapper for 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()
}

It's not the best solution, because WeakSet can be initialized with any type, and if this type doesn't conform to AnyObject protocol then app will crash with detailed reason. But I don't see any better solution right now.

Original solution was to define WeakSet in this way:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

But in this case WeakSet can't be initialized with protocol:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

Currently above code can't be compiled (Swift 2.1, Xcode 7.1).
That's why I dropped conforming to AnyObject and added additional guards with fatalError() assertions.



回答7:

How about functional style wrapper?

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)

Just call returned closure to check the target is still alive.

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

And you can store this closures into an array.

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

And you can retrieve the weakly captured values by mapping calling the closures.

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


回答8:

The existing example of the WeakContainer is helpful, but it doesn't really help one use weak references in existing swift containers such as Lists and Dictionaries.

If you want to use List methods such as contains, then the WeakContainer will need to implement Equatable. So I added the code to allow the WeakContainer to be equatable.

In case you wanted to use the WeakContainer in dictionaries, I also made it hashable so it can be used as dictionary keys.

I also renamed it to WeakObject to stress that this is only for class types and to differentiate it from the WeakContainer examples:

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

This allows you to do some cool stuff like use a Dictionary of weak references:

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


回答9:

Here's how to make @GoZoner's great answer conform to Hashable, so it can be indexed in Container objects like: 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
}


回答10:

Based on Kaz Yoshikawa answer

Details

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

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

Full Sample

Do not forget to add the solution code here


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
}

Result



回答11:

Other answers have covered the generics angle. Thought I'd share some simple code covering the nil angle.

I wanted a static array (read occasionally) of all the Labels that currently exist in the app, but didn't want to see nil's where the old ones used to be.

Nothing fancy, this is my 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))
    }
}


回答12:

Yet another solution to the same problem... the focus of this one is on storing a weak reference to an object but allowing you to store a struct too.

[I'm not sure how useful it is, but it did take a while to get the syntax right]

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


回答13:

You could create wrapper around Array. Or use this library https://github.com/NickRybalko/WeakPointerArray let array = WeakPointerArray<AnyObject>() It is type safe.



回答14:

I based this on @Eonil 's work, since I loved the closure weak-bind strategy, but I did not want to use a function operator for a variable, since it felt extremely counter intuitive

What I did, instead, is as follows:

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

This way you can do something such as:

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


回答15:

Since NSPointerArray already handles most of this automatically, I solved the problem by making a type-safe wrapper for it, which avoids a lot of the boilerplate in other answers:

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
}

Example usage:

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

It's more work up front, but the usage in the rest of your code is much cleaner IMO. If you want to make it more array-like, you can even implement subscripting, make it a SequenceType, etc. (but my project only needs append and forEach so I don't have the exact code on hand).