首页 文章

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

提问于
浏览
160

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

15 回答

  • 10

    创建一个通用包装器:

    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语言允许扩展定义为这样 .

  • 9

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

    对于Swift 3: NSHashTable.weakObjects()

    NSHashTable Class Reference

    在OS X v10.5及更高版本中可用 . 适用于iOS 6.0及更高版本 .

  • 134

    This is not my solution. 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]
    
  • 0

    派对有点迟,但试试我的 . 我实现为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
        }
    }
    

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

    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之间的行为差异超出了这个范围,但如果有人想出这个问题,我将不胜感激 .

  • 37

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

    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() 断言 .

  • 8

    功能样式包装怎么样?

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

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

    这里's how to make @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
    }
    
  • 7

    基于Kaz Yoshikawa的回答

    详情

    xCode 9.1,Swift 4

    解决方案

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

    扩展阵列

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

    用法

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

    完整样本

    不要忘记在此处添加解决方案代码


    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
    }
    

    结果

    enter image description here

  • 3

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

    我想要一个当前存在于应用程序中的所有 Label 的静态数组(偶尔读取),但是我不想看到 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))
        }
    }
    
  • 2

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

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

    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

    你可以在 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
    
  • 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 所以我手头没有确切的代码) .

相关问题