我想在Swift中存储一个弱引用数组。数组本身不应该是弱引用——它的元素应该是。我认为Cocoa NSPointerArray提供了一个非类型安全的版本。
当前回答
细节
Swift 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")
}
}
其他回答
由于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,所以我手头没有确切的代码)。
现在参加派对有点晚了,但试试我的吧。我实现为一个集合而不是数组。
WeakObjectSet
class WeakObject<T: AnyObject>: Equatable, Hashable {
weak var object: T?
init(object: T) {
self.object = object
}
var hashValue: Int {
if let object = self.object { return unsafeAddressOf(object).hashValue }
else { return 0 }
}
}
func == <T> (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
return lhs.object === rhs.object
}
class WeakObjectSet<T: AnyObject> {
var objects: Set<WeakObject<T>>
init() {
self.objects = Set<WeakObject<T>>([])
}
init(objects: [T]) {
self.objects = Set<WeakObject<T>>(objects.map { WeakObject(object: $0) })
}
var allObjects: [T] {
return objects.flatMap { $0.object }
}
func contains(object: T) -> Bool {
return self.objects.contains(WeakObject(object: object))
}
func addObject(object: T) {
self.objects.unionInPlace([WeakObject(object: object)])
}
func addObjects(objects: [T]) {
self.objects.unionInPlace(objects.map { WeakObject(object: $0) })
}
}
使用
var alice: NSString? = "Alice"
var bob: NSString? = "Bob"
var cathline: NSString? = "Cathline"
var persons = WeakObjectSet<NSString>()
persons.addObject(bob!)
print(persons.allObjects) // [Bob]
persons.addObject(bob!)
print(persons.allObjects) // [Bob]
persons.addObjects([alice!, cathline!])
print(persons.allObjects) // [Alice, Cathline, Bob]
alice = nil
print(persons.allObjects) // [Cathline, Bob]
bob = nil
print(persons.allObjects) // [Cathline]
注意WeakObjectSet不会接受String类型,而是接受NSString类型。因为,String类型不是AnyType。我的swift版本是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
上面说问题已经解决了,但我想知道这是否和这个问题有关。 无论如何,MyString或NSString之间的行为差异超出了这个上下文,但如果有人能解决这个问题,我会很感激。
细节
Swift 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")
}
}
在很多情况下,返回一个可取消的选项会更简洁。这允许调用站点决定何时显式地销毁值(以及通过解压缩隐式地销毁值):
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.
函数式样式包装器呢?
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 }
推荐文章
- 如何删除默认的导航栏空间在SwiftUI导航视图
- 如何在iOS中使用Swift编程segue
- Swift -整数转换为小时/分钟/秒
- 如何舍入一个双到最近的Int在迅速?
- 扁平化数组的数组在Swift
- Swift:声明一个空字典
- 为什么ARC仍然需要@autoreleasepool ?
- 从数组中随机选择一个元素
- Java:强/软/弱/幻影引用的区别
- 在Swift中如何调用GCD主线程上的参数方法?
- swift语言中的结构与类
- 我如何在Swift连接字符串?
- 我应该如何从字符串中删除所有的前导空格?- - - - - -斯威夫特
- 新的自动引用计数机制是如何工作的?
- Swift:理解// MARK