我希望在Swift中存储弱引用数组。这个数组本身不应该是弱引用,而它的元素应该是。我认为Cocoa的NSPointerArray
提供了一个非类型安全版本。
我希望在Swift中存储弱引用数组。这个数组本身不应该是弱引用,而它的元素应该是。我认为Cocoa的NSPointerArray
提供了一个非类型安全版本。
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
时,可以使用struct
或class
。
另外,为了帮助收割数组内容,您可以采取以下措施:
extension Array where Element:Weak<AnyObject> {
mutating func reap () {
self = self.filter { nil != $0.value }
}
}
AnyObject
应该替换为T
,但我认为当前的Swift语言不允许定义这样的扩展。count
呢? - SulthanNSHashTable<ObjectType>.weakObjectsHashTable()
对于Swift 3:NSHashTable<ObjectType>.weakObjects()
NSHashTable类参考
在OS X v10.5及更高版本中可用。
iOS 6.0及更高版本可用。
MyProtocol: class
和NSHashTable<MyProtocol>.weakObjects()
时,编译器报错了。错误信息为“'NSHashTable'要求'MyProtocol'是一个类类型”。 - GregNSHashTable
的 .count
方法存在一些缓存问题,因此将对象设置为 nil 并立即调用 count
会给出错误的值。通常在代码库中不是问题,但单元测试会因此失败。 - XmasRights无需额外的类。
只需定义一个闭包数组() -> Foo?
并使用[weak foo]
以弱引用方式捕获foo实例即可。
let foo = Foo()
var foos = [() -> Foo?]()
foos.append({ [weak foo] in return foo })
foos.forEach { $0()?.doSomething() }
filter { $0() != nil }
。 - MH175WeakArray<T> = [() -> T?]
。 - MH175虽然现在已经有点晚了,但你可以试试我的聚会。我将其实现为一个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]
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, Xcode Version 9.1 (9B55)
class WeakObject<T: AnyObject>: Equatable, Hashable {
weak var object: T?
init(object: T) {
self.object = object
}
var hashValue: Int {
if var object = object { return UnsafeMutablePointer<T>(&object).hashValue }
return 0
}
static func == (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
return lhs.object === rhs.object
}
}
class WeakObjectSet<T: AnyObject> {
var objects: Set<WeakObject<T>>
init() {
self.objects = Set<WeakObject<T>>([])
}
init(objects: [T]) {
self.objects = Set<WeakObject<T>>(objects.map { WeakObject(object: $0) })
}
var allObjects: [T] {
return objects.flatMap { $0.object }
}
func contains(_ object: T) -> Bool {
return self.objects.contains(WeakObject(object: object))
}
func addObject(_ object: T) {
self.objects.formUnion([WeakObject(object: object)])
}
func addObjects(_ objects: [T]) {
self.objects.formUnion(objects.map { WeakObject(object: $0) })
}
}
正如gokeji所提到的那样,根据使用中的代码,我发现NSString不会被释放。
我思考了一下,并编写了以下的MyString类。
// typealias MyString = NSString
class MyString: CustomStringConvertible {
var string: String
init(string: String) {
self.string = string
}
deinit {
print("relasing: \(string)")
}
var description: String {
return self.string
}
}
然后像这样将NSString
替换为MyString
。 奇怪的是,它能够运行。
var alice: MyString? = MyString(string: "Alice")
var bob: MyString? = MyString(string: "Bob")
var cathline: MyString? = MyString(string: "Cathline")
var persons = WeakObjectSet<MyString>()
persons.addObject(bob!)
print(persons.allObjects) // [Bob]
persons.addObject(bob!)
print(persons.allObjects) // [Bob]
persons.addObjects([alice!, cathline!])
print(persons.allObjects) // [Alice, Cathline, Bob]
alice = nil
print(persons.allObjects) // [Cathline, Bob]
bob = nil
print(persons.allObjects) // [Cathline]
然后我发现了一个奇怪的页面,可能与这个问题有关。
弱引用保留已释放的NSString(仅限XC9 + iOS模拟器)
https://bugs.swift.org/browse/SR-5511
它说这个问题已经被解决了,但我想知道这是否仍然与这个问题有关。 无论如何,MyString或NSString之间的行为差异超出了这个范围,但如果有人能解决这个问题,我会非常感激。
NSString
则不行。 - gokejiWeakObjectSet
类。我的使用方式是对诸如 UITableViews
、UICollectionViews
之类的 UIKit 组件进行弱引用。这些组件实现了一个名为 A
的协议,在我的类中,我声明了一个弱集合,像这样:var set = WeakObjectSet<A>()
。当 ViewController 取消显示并且其视图被移除时,set
的表现是正确的,但是内存工具显示在 specialized WeakObject.init(object:)
处发生了泄漏。 - Guy DaherUnsafeMutablePointer<T>(&object)
返回的指针可能会随机更改(与withUnsafePointer
相同)。 我现在使用由NSHashTable
支持的版本:https://gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d。 - simonseyerhashValue
的值会发生变化。我已经更新了最近Xcode和Swift的WeakObjectSet代码。WeakObjectSet.swift - Swift 5
https://gist.github.com/codelynx/73919293f4166edd767f77a4cd178274 - Kaz Yoshikawastruct 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]
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 }
var array: [(x: Int, y: () -> T?)]
。这正是我一直在寻找的。 - jboilet values = Array(array1.map({ $0() }))
部分我不是很确定。由于这不再是一个带有弱引用的闭包数组,所以只有在该数组被释放时,values 才会被保留。如果我的理解是正确的,那么需要注意的是,你永远不应该像 self.items = Array(array1.map({ $0() }))
这样保留该数组,因为这会打破它的目的。 - Matic Oblak您可以创建一个包装对象来保存一个弱指针,来实现该功能。
struct WeakThing<T: AnyObject> {
weak var value: T?
init (value: T) {
self.value = value
}
}
var weakThings = WeakThing<Foo>[]()
protocol Protocol : class { ... }
- olejnjakstruct WeakObject<Object: AnyObject> { weak var object: Object? }
@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) }
}
}
class Class1 { // or struct
@WeakElements var weakObjectsArray = [UIView?]() // Use like regular array. With any objects
func test() {
weakObjectsArray.append(UIView())
weakObjectsArray.forEach { print($0) }
}
}
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) })
}
}
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()
var b = WeakObjectsArray()
}
- Matic Oblakprotocol TP: class { }
class TC where TYPE: TP {
var a = WeakObjectsArray() // 像普通数组一样使用,可以存储任何对象
var weakObjectsArray = [TYPE?]()
}
- Vasily Bodnarchukdelegates
的单例。然后,您将有一些视图控制器希望使用此功能。您期望调用MyManager.delegates.append(self)
。但是,如果MyManager
被锁定为某种通用类型,则这不太可用。 - Matic Oblakprotocol 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())
- Vasily Bodnarchuk我有一个相同的想法,即使用泛型创建弱容器。
因此,我为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()
断言。
由于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
一开始可能需要更多的工作,但是在代码的其他部分使用起来会更加干净。如果你想让它更像一个数组,甚至可以实现下标操作,将其变为SequenceType
等(但我的项目只需要append
和forEach
,所以我手头没有确切的代码)。
get
函数中要小心索引。 - Roman Filippov
Array<weak AnyObject>
吗? - Cy-4AH