ChatGPT解决这个技术问题 Extra ChatGPT

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

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

如何创建一个弱引用另一个对象的容器对象,然后创建一个数组? (如果你没有得到更好的答案)
你为什么不使用 NSPointerArray ?
@nielsbot 那是一个旧的 obj-c 解决方案 :) 为了让它变得 Swifty,它应该是一个通用对象! :) 然而,真正的问题是当引用的对象被释放时如何从数组中删除对象。
对,我更喜欢参数化类型的东西。我想我可以围绕 NSPointerArray 制作一个参数化包装器,但想看看是否有任何替代方案。
作为另一种选择,NSHashTable 存在。它基本上是一个 NSSet,允许您指定它应该如何引用它包含的对象。

G
GoZoner

创建一个通用包装器:

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


当它们的值被释放时,如何从数组中删除包装器对象?
是的,它使编译器崩溃。
@GoZoner 如果您需要 count 怎么办?
请在新问题中发布您的问题代码;当它可能是您的代码时,没有理由回答我的答案!
@EdGamble 提供的代码按原样工作,但如果将类 Stuff 替换为协议则失败;见this related question
m
malhal

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

对于 Swift 3:NSHashTable<ObjectType>.weakObjects()

NSHashTable Class Reference

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


最佳答案,不要为包装纸浪费时间!
这很聪明,但就像 GoZoner 的回答一样,这不适用于 Any 而不是 AnyObject 的类型,例如协议。
@SteveWilford但是一个协议可以由一个类实现,这将使它成为一个引用类型
协议可以扩展类,然后您可以将其用作弱协议(例如协议 MyProtocol: 类)
我收到 MyProtocol: classNSHashTable<MyProtocol>.weakObjects() 的编译器错误。 “'NSHashTable' 要求 'MyProtocol' 是一个类类型。
f
frouo

函数式编程方法

不需要额外的课程。

只需定义一组闭包 () -> Foo? 并使用 [weak foo] 将 foo 实例捕获为弱。

let foo = Foo()

var foos = [() -> Foo?]()
foos.append({ [weak foo] in return foo })

foos.forEach { $0()?.doSomething() }

非常好。我认为您丢失了 compactMap 并且需要 filter { $0() != nil }
如果您想要更方便/描述性的语法,您还可以声明类型别名 WeakArray<T> = [() -> T?]
K
Kaz Yoshikawa

派对有点晚了,但试试我的。我实现为 Set 而不是 Array。

弱对象集

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

** 添加于 2017 年 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 之间的行为差异超出了此上下文,但如果有人解决了这个问题,我将不胜感激。


我已经为我的项目采用了这个解决方案。很好!只是一个建议,此解决方案似乎没有从内部 Set 中删除 nil 值。因此,我添加了顶部答案中提到的 reap() 函数,并确保每次访问 WeakObjectSet 时都调用 reap()
嗯等等,由于某种原因,这在 Swift 4/iOS 11 中不起作用。似乎当值变为 nil 时,弱引用不会立即被释放
我将代码更新为 Swift4,请参阅答案的后半部分。我似乎 NSString 有一些释放问题,但它应该仍然适用于您的自定义类对象。
非常感谢@KazYoshikawa 调查它并更新答案!后来我也意识到自定义类可以工作,而 NSString 不能。
我体验过 UnsafeMutablePointer<T>(&object) 返回的指针可以随机变化(与 withUnsafePointer 相同)。我现在使用由 NSHashTable 支持的版本:gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d
r
rjkaplan

这不是我的解决方案。 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]

奇怪,但不再适用于结构。为我说EXC_BAD_ACCESS。上课就好了
结构是值类型,它不应该与它们一起使用。它在运行时崩溃而不是编译时错误的事实是编译器错误。
J
Joshua Weinberg

您可以通过创建一个包装对象来保存弱指针来做到这一点。

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

然后在数组中使用这些

var weakThings = WeakThing<Foo>[]()

必须是 class 才能使用 weak 变量
谁说的?上面的代码对我来说很好。唯一的要求是变弱的对象需要是一个类,而不是持有弱引用的对象
对不起。我可以发誓我刚刚收到一条编译器消息,上面写着“不能在结构中使用弱变量”。你是对的 - 编译。
@JoshuaWeinberg 如果 Foo 是一个协议怎么办?
@onmyway133 AFAIK,如果协议被声明为仅由它可以工作的类实现。 protocol Protocol : class { ... }
e
eonil

功能样式包装器怎么样?

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

实际上,您不需要一个函数来进行闭包。直接捕获对象即可。

let captured3 = { [weak obj3] in return obj3 }

问题是如何创建弱对象的数组(或说 Set)。
使用此解决方案,您甚至可以创建具有多个值的数组,例如 var array: [(x: Int, y: () -> T?)]。正是,我在寻找什么。
@DavidH 我更新了我的答案来回答这个问题。我希望这有帮助。
我喜欢这种方法,我认为它非常聪明。我使用这种策略做了一个类实现。谢谢!
不太确定 let values = Array(array1.map({ $0() })) part。由于这不再是具有弱引用的闭包数组,因此值将被保留,直到该数组被释放。如果我是正确的,那么重要的是要注意你永远不应该像 self.items = Array(array1.map({ $0() })) 这样保留这个数组,因为这超出了目的。
V
Vasily Bodnarchuk

细节

斯威夫特 5.1,Xcode 11.3.1

解决方案

struct WeakObject<Object: AnyObject> { weak var object: Object? }

选项1

@propertyWrapper
struct WeakElements<Collect, Element> where Collect: RangeReplaceableCollection, Collect.Element == Optional<Element>, Element: AnyObject {
    private var weakObjects = [WeakObject<Element>]()

    init(wrappedValue value: Collect) { save(collection: value) }

    private mutating func save(collection: Collect) {
        weakObjects = collection.map { WeakObject(object: $0) }
    }

    var wrappedValue: Collect {
        get { Collect(weakObjects.map { $0.object }) }
        set (newValues) { save(collection: newValues) }
    }
}

选项 1 用法

class Class1 { // or struct
    @WeakElements var weakObjectsArray = [UIView?]() // Use like regular array. With any objects

    func test() {
        weakObjectsArray.append(UIView())
        weakObjectsArray.forEach { print($0) }
    }
}

选项 2

struct WeakObjectsArray<Object> where Object: AnyObject {
    private var weakObjects = [WeakObject<Object>]()
}

extension WeakObjectsArray {
    typealias SubSequence = WeakObjectsArray<Object>
    typealias Element = Optional<Object>
    typealias Index = Int
    var startIndex: Index { weakObjects.startIndex }
    var endIndex: Index { weakObjects.endIndex }
    func index(after i: Index) -> Index { weakObjects.index(after: i) }
    subscript(position: Index) -> Element {
        get { weakObjects[position].object }
        set (newValue) { weakObjects[position] = WeakObject(object: newValue) }
    }
    var count: Int { return weakObjects.count }
    var isEmpty: Bool { return weakObjects.isEmpty }
}

extension WeakObjectsArray: RangeReplaceableCollection {
    mutating func replaceSubrange<C : Collection>( _ subrange: Range<Index>, with newElements: C) where Element == C.Element {
        weakObjects.replaceSubrange(subrange, with: newElements.map { WeakObject(object: $0) })
    }
}

选项 2 用法

class Class2 { // or struct
    var weakObjectsArray = WeakObjectsArray<UIView>() // Use like regular array. With any objects

    func test() {
        weakObjectsArray.append(UIView())
        weakObjectsArray.forEach { print($0) }
    }
}

完整样本

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

import UIKit

class ViewController: UIViewController {

    @WeakElements var weakObjectsArray = [UIView?]()
    //var weakObjectsArray = WeakObjectsArray<UIView>()

    override func viewDidLoad() {
        super.viewDidLoad()
        addSubviews()
    }

    private func printArray(title: String) {
        DispatchQueue.main.async {
            print("=============================\n\(title)\ncount: \(self.weakObjectsArray.count)")
            self.weakObjectsArray.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() {
        (0...1).forEach { _ in addView() }
        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.first { view -> Bool in return !(view is UIButton) }?
            .removeFromSuperview()

        printArray(title: "First view deleted")
    }

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

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

我对这两个选项(以及许多其他选项)的问题是这些类型的数组不适用于协议。例如,这不会编译:protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
@MaticOblak 使用泛型怎么样? protocol TP: class { } class TC<TYPE> where TYPE: TP { var a = WeakObjectsArray<TYPE>() // Use like regular array. With any objects var weakObjectsArray = [TYPE?]() }
这个想法是这个数组可以保存实现相同类协议的不同类型的对象。通过使用泛型,您可以将其锁定为单一类型。例如,想象有一个包含 delegates 这样的数组的单例。然后你会有一些想要使用这个功能的视图控制器。您希望调用 MyManager.delegates.append(self)。但是如果 MyManager 被锁定为某个泛型类型,那么这不是很有用。
@MaticOblak 好的。试试这个:protocol TP: class { } class MyManager { typealias Delegate = AnyObject & TP static var delegates = [Delegate?]() } class A: TP { } class B: TP { } //MyManager.delegates.append(A()) //MyManager.delegates.append(B())
您现在丢失了数组的通用部分,这有点重要:) 我觉得这是不可行的。目前 Swift 的一个限制......
V
Vlad Papko

我有同样的想法来创建使用泛型的弱容器。
结果我为 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() 断言的额外保护。


呵呵只是用于 hashtable.allObjects 中的对象
R
Roman Filippov

由于 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: index) {
            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 所以我手头没有确切的代码)。


小心 get 函数中的索引
这很奇怪,在我的实际代码中是正确的,所以我不知道这个错误是如何进入答案的。
T
Tod Cunningham

WeakContainer 的现有示例很有帮助,但它并不能真正帮助在现有的 swift 容器(如列表和字典)中使用弱引用。

如果你想使用 List 方法,例如 contains,那么 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)
}

S
Sakiboy

以下是如何使 @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
}

D
Dan Rosenstark

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

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

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

N
Nick Rybalko

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


D
Dan

这是一个类型安全的集合,包含弱对象的容器。它还会在访问时自动删除容器/包装器。

例子:

protocol SomeDelegate: class {
    func doSomething()
}

class SomeViewController: UIViewController {
    var delegates: WeakCollection<SomeDelegate> = []

    func someFunction(delegate: SomeDelegate) {
        delegates.append(delegate)
    }

    func runDelegates() {
        delegates.forEach { $0.doSomething() }
    }
}

自定义集合 https://gist.github.com/djk12587/46d85017fb3fad6946046925f36cefdc

import Foundation

/**
 Creates an array of weak reference objects.
 - Important:
    Because this is an array of weak objects, the objects in the array can be removed at any time.
    The collection itself will handle removing nil objects (garbage collection) via the private function cleanUpNilContainers()
 */

class WeakCollection<T>: RangeReplaceableCollection, ExpressibleByArrayLiteral {
    typealias Index = Int
    typealias Element = T
    typealias Iterator = IndexingIterator<[Element]>

    private var weakContainers: [WeakReferenceContainer]

    required convenience init(arrayLiteral: Element...) {
        self.init()
        self.weakContainers = WeakCollection.createWeakContainers(from: arrayLiteral)
    }

    required init() {
        weakContainers = []
    }

    required init<S>(_ elements: S) where S: Sequence, WeakCollection.Element == S.Element {
        self.weakContainers = WeakCollection.createWeakContainers(from: elements)
    }

    static private func createWeakContainers<S>(from weakCollection: S) -> [WeakReferenceContainer] where S: Sequence,
        WeakCollection.Element == S.Element {
            return weakCollection.compactMap { WeakReferenceContainer(value: $0 as AnyObject) }
    }

    func append<S>(contentsOf newElements: S) where S: Sequence, WeakCollection.Element == S.Element {
        self.weakContainers.append(contentsOf: WeakCollection.createWeakContainers(from: newElements))
    }

    var startIndex: Index {
        return references.startIndex
    }

    var endIndex: Index {
        return references.endIndex
    }

    func replaceSubrange<C, R>(_ subrange: R, with newElements: C) where
        C: Collection, R: RangeExpression, WeakCollection.Element == C.Element, WeakCollection.Index == R.Bound {
            weakContainers.replaceSubrange(subrange, with: WeakCollection.createWeakContainers(from: newElements))
    }

    func index(after i: Int) -> Int {
        return references.index(after: i)
    }

    func makeIterator() -> IndexingIterator<[Element]> {
        return references.makeIterator()
    }

    subscript(index: Int) -> Element {
        get {
            return references[index]
        }
        set {
            weakContainers[index] = WeakReferenceContainer(value: newValue as AnyObject)
        }
    }
}

extension WeakCollection {
    private class WeakReferenceContainer {
        private(set) weak var value: AnyObject?

        init(value: AnyObject?) {
            self.value = value
        }
    }

    private func cleanUpNilContainers() {
        weakContainers = weakContainers.compactMap { $0.value == nil ? nil : $0 }
    }

    private var references: [Element] {
        cleanUpNilContainers()
        return weakContainers.compactMap { $0.value as? T }
    }
}

B
Berik

在许多情况下,返回可取消的会更干净。这允许调用站点决定何时显式销毁值(以及通过 descoping 隐式销毁):


public protocol Cancellable {
    func cancel()
}

private struct MyValue: Identifiable {
    let id: String
    // ...
}

private class CancellationHandler: Cancellable {
    let handler: () -> ()
    init(handler: @escaping () -> ()) { self.handler = handler }
    func cancel() { handler() }
    deinit { handler() }
}

public class Container {
    private var array = [MyType]()

    public func add() -> Cancellable {
        let value = MyValue(...)
        array.append(value)
        return CancellationHandler {
            array.removeFirst(where: { $0.id == value.id })
        }
    }
}

let cancellable = container.add()

// Both cancellable.cancel() and the cancellable descoping 
// will call the `cancel` function, removing the value from array.

w
wils

其他答案涵盖了泛型角度。以为我会分享一些涵盖 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))
    }
}

flatMap 代替 filter 怎么样? map ?
A
Ale Ravasio

我基于@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

Y
YannSteph

这是我的解决方案:

释放时清理数组,因为 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)) }
    }
}

B
Bob Rice

感谢这里提供的所有建议解决方案。由于强数组引用,我遇到了同样的内存泄漏问题,并且对象包装器解决方案运行良好。一个注意事项;我看到了通过将数组替换为过滤数组来压缩数组的示例。到目前为止,我更喜欢使用 removeAll 方法,例如,receiveLinks.removeAll( where: { $0.receiveLink == receiveLink } ) 我一直假设 Array 使用 NSMutableArray 并且替换 Array 对象与从中删除元素不同数组,尤其是当数组有多个引用时。在我看来, removeAll 会更有效率。