如何在Swift中声明一个弱引用数组?

问题描述 投票:164回答:16

我想在Swift中存储一组弱引用。数组本身不应该是弱引用 - 它的元素应该是。我认为Cocoa NSPointerArray提供了非类型安全版本。

swift automatic-ref-counting weak-references nspointerarray
16个回答
137
投票

创建一个通用包装器:

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

将此类的实例添加到您的数组。

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

在定义Weak时,您可以使用structclass

此外,为了帮助获取数组内容,您可以执行以下操作:

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

上面的AnyObject的使用应该用T替换 - 但我不认为当前的Swift语言允许扩展定义为这样。


2
投票

以下是如何使@ GoZoner的答案符合Hashable,因此它可以在Container对象中编入索引,例如:SetDictionaryArray等。

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
}

1
投票

其他答案涵盖了泛型角度。以为我会分享一些涵盖nil角度的简单代码。

我想要一个当前存在于应用程序中的所有Labels的静态数组(偶尔读取),但是不想看到旧的那些曾经是nil的地方。

没什么好看的,这是我的代码......

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

1
投票

针对同一问题的又一个解决方案......这个问题的重点是存储对象的弱引用,但也允许存储结构。

[我不确定它有多有用,但确实需要一段时间才能使语法正确]

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

1
投票

由于NSPointerArray已经自动处理了大部分内容,因此我通过为其设置类型安全包装来解决问题,这避免了其他答案中的许多样板:

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
}

用法示例:

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

它预先做的更多,但在其余代码中的使用更加清晰IMO。如果你想让它更像数组,你甚至可以实现下标,使它成为SequenceType等(但我的项目只需要appendforEach所以我手头没有确切的代码)。


0
投票

你可以在Array周围创建包装器。或使用此库https://github.com/NickRybalko/WeakPointerArray let array = WeakPointerArray<AnyObject>() 它是类型安全的。


0
投票

我基于@Eonil的工作,因为我喜欢闭包弱绑定策略,但我不想使用函数运算符来变量,因为它感觉非常反直觉

相反,我所做的如下:

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

通过这种方式,您可以执行以下操作:

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

0
投票

我的解决方案:

  • 解除分配时清理数组,因为WeakObjectSet正在存储而不是从WeakObject中删除
  • 在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)) }
    }
}

43
投票

您可以将NSHashTable与weakObjectsHashTable一起使用。 NSHashTable.weakObjectsHashTable()

对于Swift 3:NSHashTable.weakObjects()

NSHashTable Class Reference

适用于OS X v10.5及更高版本。

适用于iOS 6.0及更高版本。


10
投票

这不是我的解决方案。 I found it on the Apple Developer Forums

@GoZoner有一个很好的答案,但它崩溃了Swift编译器。

这是一个弱对象容器的版本不会崩溃当前发布的编译器。

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

    init (value: T) {
        _value = value
    }

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

然后,您可以创建这些容器的数组:

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

10
投票

派对有点迟,但试试我的。我实现为Set而不是数组。

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

用法

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]

请注意WeakObjectSet不会采用String类型而是采用NSString。因为,String类型不是AnyType。我的快速版本是Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)

代码可以从Gist中获取。 https://gist.github.com/codelynx/30d3c42a833321f17d39

**于2010年11月上市

我将代码更新为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) })
    }
}

正如gokeji所提到的,我发现NSString不会根据使用中的代码取消分配。我摸不着头脑,按照以下方式写了MyString类。

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

然后像这样用NSString替换MyString。然后奇怪地说它有效。

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]

然后我发现一个奇怪的页面可能与此问题有关。

弱引用保留解除分配的NSString(仅限XC9 + iOS Sim)

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

它说问题是RESOLVED,但我想知道这是否仍然与这个问题有关。无论如何,MyString或NSString之间的行为差​​异超出了这个范围,但如果有人想出这个问题,我将不胜感激。


9
投票

您可以通过创建包装器对象来保存弱指针来完成此操作。

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

然后在阵列中使用这些

var weakThings = WeakThing<Foo>[]()

7
投票

我有同样的想法用泛型创建弱容器。 结果我为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?
        }
    }
}

用法:

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

这不是最好的解决方案,因为WeakSet可以用任何类型初始化,如果这种类型不符合AnyObject协议,那么应用程序将崩溃,详细的原因。但我现在没有看到任何更好的解决方案。

最初的解决方案是以这种方式定义WeakSet

class WeakSet<ObjectType: AnyObject>: SequenceType {}

但在这种情况下,WeakSet无法使用协议进行初始化:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

目前上面的代码无法编译(Swift 2.1,Xcode 7.1)。 这就是为什么我放弃符合AnyObject并添加额外的警卫与fatalError()断言。


7
投票

功能样式包装怎么样?

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)

只需调用返回的闭包来检查目标是否仍然存活。

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

您可以将此闭包存储到数组中。

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

并且您可以通过映射调用闭包来检索弱捕获的值。

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

3
投票

WeakContainer的现有示例很有用,但它并没有真正帮助在现有的swift容器(如Lists和Dictionaries)中使用弱引用。

如果要使用List等方法,那么WeakContainer将需要实现Equatable。所以我添加了代码以允许WeakContainer相等。

如果你想在字典中使用WeakContainer,我也使它可以使用它,因此它可以用作字典键。

我还将它重命名为WeakObject,强调这仅适用于类类型,并将其与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!
}

这允许你做一些很酷的东西,比如使用弱引用词典:

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

3
投票

Basse d on“K z Yoshikawa”“1”Anusu r

细节

  • Xcode 10.2(10E125),Swift 5

import Foundation

protocol Weakable: class {
    associatedtype T: AnyObject = Self
    var asWeakValue: WeakObject<T> { get }
}

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: 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 where Element: AnyObject  {

    var asWeakArray: Array<WeakObject<Element>> {
        var weakArray = [WeakObject<Element>]()
        for item in self {
            let obj = WeakObject(object: item)
            weakArray.append(obj)
        }
        return weakArray
    }
}

用法

extension UIView: Weakable {
    var asWeakValue: WeakObject<UIView> { return WeakObject(object: self) }
}

var weakArray = [WeakObject<UIView>]()
weakArray = view.subviews.asWeakArray
weakArray.append(view.asWeakValue)

完整样本

不要忘记粘贴解决方案代码

import UIKit

class ViewController: UIViewController {

    private var weakArray = [WeakObject<UIView>]()
    override func viewDidLoad() {
        super.viewDidLoad()
        addSubviews()
        weakArray = view.subviews.asWeakArray
        weakArray.append(createRandomRectangleAndAdd(to: view).asWeakValue)
    }

    private func printArray(title: String) {
        print("=============================\n\(title)\ncount: \(weakArray.count)")
        weakArray.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() {

        _ = createRandomRectangleAndAdd(to: view)
        _ = createRandomRectangleAndAdd(to: view)

        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
            .filter { view -> Bool in return !(view is UIButton) }
            .first?.removeFromSuperview()

        view.layoutIfNeeded()
        printArray(title: "First view deleted")
    }

    @objc func addView() {
        weakArray.append(createRandomRectangleAndAdd(to: view).asWeakValue)
        printArray(title: "View addded")
    }

    @objc func removeNils() {
        weakArray = weakArray.filter { $0.value != nil }
        printArray(title: "Remove all nil elements in weakArray")
    }
}

extension UIView: Weakable {
    var asWeakValue: WeakObject<UIView> { return WeakObject(object: self) }
}
© www.soinside.com 2019 - 2024. All rights reserved.