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.
问题:
回答1:
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.
回答2:
You can use the NSHashTable with weakObjectsHashTable. NSHashTable.weakObjectsHashTable()
For Swift 3: NSHashTable.weakObjects()
NSHashTable Class Reference
Available in OS X v10.5 and later.
Available in iOS 6.0 and later.
回答3:
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]
回答4:
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.
回答5:
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>[]()
回答6:
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.
回答7:
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() }))
回答8:
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)
}
回答9:
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
}
回答10:
Based on Kaz Yoshikawa answer
Details
xCode 9.1, Swift 4
Solution
WeakObject
import Foundation
protocol WeakObjectProtocol {
associatedtype WeakObjectType
var value: WeakObjectType? {get set}
init(object: WeakObjectType)
}
class WeakObject<T: AnyObject>: WeakObjectProtocol {
typealias WeakObjectType = T
weak var value: WeakObjectType?
required init(object: WeakObjectType) {
self.value = object
}
var referenceCount: Int {
return CFGetRetainCount(value)
}
}
extension WeakObject: Equatable {
static func == (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
return lhs.value === rhs.value
}
}
extension WeakObject: Hashable {
var hashValue: Int {
if var value = value { return UnsafeMutablePointer<T>(&value).hashValue }
return 0
}
}
extension WeakObject: CustomStringConvertible {
var description: String {
if let value = value {
let className = String(describing: type(of: value.self))
return "{class: \(className); referenceCount: \(referenceCount)}"
}
return "nil"
}
}
extension Array
import Foundation
extension Array where Element: AnyObject {
var weak: Array<WeakObject<Element>> {
var weakArray = [WeakObject<Element>]()
for item in self {
let obj = WeakObject(object: item)
weakArray.append(obj)
}
return weakArray
}
}
extension Array where Element: WeakObjectProtocol {
typealias EnumeratedWeakObjectClosure = (_ index: Int, _ value: Element.WeakObjectType?)->()
typealias WeakObjectClosure = (_ value: Element.WeakObjectType?)->()
mutating func removeNils() {
self = self.flatMap{ (element) -> Element? in
if element.value == nil {
return nil
}
return element
}
}
mutating func append(weakValue: Element.WeakObjectType) {
append(Element(object: weakValue))
}
subscript(index: Int) -> Element.WeakObjectType? {
get {
return self[index].value
}
}
func `for` (closure: WeakObjectClosure){
for item in self {
closure(item.value)
}
}
func forEnumerated (closure: EnumeratedWeakObjectClosure) {
for (index,item) in self.enumerated() {
closure(index, item.value)
}
}
mutating func remove(index: Int, closure: EnumeratedWeakObjectClosure) {
closure(index, self[index].value)
remove(at: index)
}
mutating func remove(index: Int, closure: WeakObjectClosure) {
closure(self[index].value)
remove(at: index)
}
}
Usage
// Array of week objects
var weakArray = [WeakObject<UIView>]()
// Get array of week objects (transfom from [AnyObject])
// way 1
weakArray = view.subviews.weak
// way 2
weakArray = [view.subviews[0], view.subviews[1]].weak
// Add single element to the end of the array
weakArray.append(weakValue: UIView())
// For loop
weakArray.for { (element) in
print("\(String(describing: element))")
}
// For loop with index (position number)
weakArray.forEnumerated { (index, element) in
print("\(index) \(String(describing: element))")
}
Full Sample
Do not forget to add the solution code here
ViewController
import UIKit
class ViewController: UIViewController {
var weakArray = [WeakObject<UIView>]()
override func viewDidLoad() {
super.viewDidLoad()
addSubviews()
weakArray = view.subviews.weak
weakArray.append(weakValue: generateView())
weakArray.remove(index: 0) { item in
item?.removeFromSuperview()
}
weakArray.for { (element) in
print("\(String(describing: element))")
}
}
func printArray(title: String) {
print("=============================\n\(title)\ncount: \(weakArray.count)")
weakArray.forEnumerated { (index,element) in
print("\(index) \(String(describing: element))")
}
}
}
// Creating views
extension ViewController {
func generateView() -> UIView {
let randomValue: ()->(CGFloat) = { return CGFloat(rand[50, 300]) }
let view = UIView(frame: CGRect(x: randomValue(), y: randomValue(), width: randomValue(), height: randomValue()))
view.backgroundColor = .blue
let randomColorComponent: ()->(CGFloat) = { return CGFloat(rand[0, 255])/CGFloat(255) }
let color = UIColor(red: randomColorComponent(), green: randomColorComponent(), blue: randomColorComponent(), alpha: 1)
view.backgroundColor = color
self.view.addSubview(view)
return view
}
func addSubviews() {
_ = generateView()
_ = generateView()
addButtons()
}
}
// Buttons
extension ViewController {
func addButtons() {
var button = UIButton(frame: CGRect(x: 10, y: 20, width: 40, height: 40))
button.setTitle("Add", for: .normal)
button.addTarget(self, action: #selector(addView), for: .touchUpInside)
button.setTitleColor(.blue, for: .normal)
view.addSubview(button)
button = UIButton(frame: CGRect(x: 60, y: 20, width: 60, height: 40))
button.setTitle("Delete", for: .normal)
button.addTarget(self, action: #selector(deleteView), for: .touchUpInside)
button.setTitleColor(.blue, for: .normal)
view.addSubview(button)
button = UIButton(frame: CGRect(x: 120, y: 20, width: 100, height: 40))
button.setTitle("Remove nil", for: .normal)
button.addTarget(self, action: #selector(removeNils), for: .touchUpInside)
button.setTitleColor(.blue, for: .normal)
view.addSubview(button)
}
@objc func deleteView() {
view.subviews.filter { view -> Bool in
return !(view is UIButton)
}.first?.removeFromSuperview()
DispatchQueue.main.async {
self.view.layoutIfNeeded()
self.printArray(title: "First view deleted")
}
}
@objc func addView() {
weakArray.append(weakValue: generateView())
printArray(title: "View addded")
}
@objc func removeNils() {
weakArray.removeNils()
printArray(title: "Remove all nil elements in weakArray")
}
}
Rand func
class Random {
subscript<T>(_ min: T, _ max: T) -> T where T : BinaryInteger {
get {
return rand(min-1, max+1)
}
}
}
let rand = Random()
func rand<T>(_ min: T, _ max: T) -> T where T : BinaryInteger {
let _min = min + 1
let difference = max - _min
return T(arc4random_uniform(UInt32(difference))) + _min
}
Result
回答11:
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 Label
s 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))
}
}
回答12:
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
回答13:
You could create wrapper around Array
. Or use this library https://github.com/NickRybalko/WeakPointerArray
let array = WeakPointerArray<AnyObject>()
It is type safe.
回答14:
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
回答15:
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: 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
}
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).