How do I declare an array of weak references in Sw

2019-01-07 03:55发布

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.

15条回答
2楼-- · 2019-01-07 04:07

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.

查看更多
欢心
3楼-- · 2019-01-07 04:08

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>[]()
查看更多
倾城 Initia
4楼-- · 2019-01-07 04: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 affffded")
    }

    @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

enter image description here

查看更多
Luminary・发光体
5楼-- · 2019-01-07 04:12

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))
    }
}
查看更多
我只想做你的唯一
6楼-- · 2019-01-07 04:13

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
查看更多
对你真心纯属浪费
7楼-- · 2019-01-07 04:13

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
查看更多
登录 后发表回答