How do I declare an array of weak references in Swift?

SwiftAutomatic Ref-CountingWeak ReferencesNspointerarray

Swift Problem Overview


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.

Swift Solutions


Solution 1 - Swift

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.

Solution 2 - Swift

You can use the NSHashTable with weakObjectsHashTable. NSHashTable<ObjectType>.weakObjectsHashTable()

For Swift 3: NSHashTable<ObjectType>.weakObjects()

NSHashTable Class Reference

> Available in OS X v10.5 and later. > > Available in iOS 6.0 and later.

Solution 3 - Swift

A functional programming approach

No extra class needed.

Simply define an array of closures () -> Foo? and capture the foo instance as weak using [weak foo].

let foo = Foo()

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

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

Solution 4 - Swift

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.

Solution 5 - Swift

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]

Solution 6 - Swift

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>[]()

Solution 7 - Swift

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

Actually, you don't need a function to make a closure. Just capture an object directly.

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

Solution 8 - Swift

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.

Solution 9 - Swift

Details

  • Swift 5.1, Xcode 11.3.1

Solution

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

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

Option 1 usage

class Class1 { // or struct
    @WeakElements var weakObjectsArray = [UIView?]() // Use like regular array. With any objects
    
    func test() {
        weakObjectsArray.append(UIView())
        weakObjectsArray.forEach { print($0) }
    }
}

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

Option 2 usage

class Class2 { // or struct
    var weakObjectsArray = WeakObjectsArray<UIView>() // Use like regular array. With any objects
    
    func test() {
        weakObjectsArray.append(UIView())
        weakObjectsArray.forEach { print($0) }
    }
}

Full sample

> do not forget to paste solution code

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

Solution 10 - Swift

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

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

Solution 11 - Swift

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

Solution 12 - Swift

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
}

Solution 13 - Swift

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

Solution 14 - Swift

You could create wrapper around Array. Or use this library https://github.com/NickRybalko/WeakPointerArray

let array = WeakPointerArray<AnyObject>()

It is type safe.

Solution 15 - Swift

This is a type safe collection that holds containers of weak objects. It also auto removes nil the containers/wrappers when it's accessed.

Example:

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

The custom collection 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 }
    }
}

Solution 16 - Swift

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

Solution 17 - Swift

In many cases it is more clean to return a cancellable. This allows the call-site to decide when to destroy the value explicitly (as well as implicitly by 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.

Solution 18 - Swift

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

Solution 19 - Swift

This my solution:

  • Clean up array when deallocated, because WeakObjectSet are storing and not getting off WeakObject
  • Resolve the fatal error when duplicate element found in 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)) }
    }
}

Solution 20 - Swift

Thanks for all of the suggested solutions here. I had the same memory leak problems due to strong array references and the object wrapper solutions works well. One note though; I see examples where arrays are condensed by replacing them with a filtered array. Thus far, I have preferred to use the removeAll method, e.g., receiveLinks.removeAll( where: { $0.receiveLink == receiveLink } ) I have been assuming that Array uses NSMutableArray and replacing an Array object is not the same as removing elements from the array especially if the array has several references to it. Seems to me that removeAll would be more efficient.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBillView Question on Stackoverflow
Solution 1 - SwiftGoZonerView Answer on Stackoverflow
Solution 2 - SwiftThierryView Answer on Stackoverflow
Solution 3 - SwiftfrouoView Answer on Stackoverflow
Solution 4 - SwiftKaz YoshikawaView Answer on Stackoverflow
Solution 5 - SwiftrjkaplanView Answer on Stackoverflow
Solution 6 - SwiftJoshua WeinbergView Answer on Stackoverflow
Solution 7 - SwifteonilView Answer on Stackoverflow
Solution 8 - SwiftVlad PapkoView Answer on Stackoverflow
Solution 9 - SwiftVasily BodnarchukView Answer on Stackoverflow
Solution 10 - SwiftJohn MontgomeryView Answer on Stackoverflow
Solution 11 - SwiftTod CunninghamView Answer on Stackoverflow
Solution 12 - SwiftSakiboyView Answer on Stackoverflow
Solution 13 - SwiftDan RosenstarkView Answer on Stackoverflow
Solution 14 - SwiftNick RybalkoView Answer on Stackoverflow
Solution 15 - SwiftDanView Answer on Stackoverflow
Solution 16 - SwiftwilsView Answer on Stackoverflow
Solution 17 - SwiftBerikView Answer on Stackoverflow
Solution 18 - SwiftAle RavasioView Answer on Stackoverflow
Solution 19 - SwiftYannStephView Answer on Stackoverflow
Solution 20 - SwiftBob RiceView Answer on Stackoverflow