Я хочу сохранить массив слабых ссылок в 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 допускает расширение, определенное как таковое.
Вы можете использовать NSHashTable со значением weakObjectsHashTable. NSHashTable.weakObjectsHashTable()
Для Swift 3: NSHashTable.weakObjects()
Доступно в OS X v10.5 и более поздних версиях.
Доступно в iOS 6.0 и более поздних версиях.
Any
но не AnyObject
, такими как протоколы.
Это вроде поздно для вечеринки, но попробуйте мой. Я реализовал как набор не массив.
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
** ДОБАВЛЕН В NOV.2017
Я обновил код до 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
}
}
Затем замените 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 Sim)
https://bugs.swift.org/browse/SR-5511
В нем говорится, что проблема RESOLVED
, но мне интересно, связано ли это с этой проблемой.
В любом случае различия поведения между MyString или NSString выходят за рамки этого контекста, но я был бы признателен, если бы кто-то понял эту проблему.
Это не мое решение. Я нашел его на форумах разработчиков Apple.
@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
для меня. С классом работает просто отлично
Вы можете сделать это, создав объект-оболочку, чтобы удерживать слабый указатель.
struct WeakThing<T: AnyObject> {
weak var value: T?
init (value: T) {
self.value = value
}
}
И затем используя их в массиве
var weakThings = WeakThing<Foo>[]()
class
чтобы использовать weak
переменные
У меня была та же идея создать слабый контейнер с дженериками.
В результате я создал обертку для 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()
.
Как насчет оболочки функционального стиля?
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() }))
var array: [(x: Int, y: () -> T?)]
. Именно то, что я искал.
Существующий пример WeakContainer полезен, но на самом деле это не помогает использовать слабые ссылки в существующих быстрых контейнерах, таких как списки и словари.
Если вы хотите использовать методы List, такие как содержит, то 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)
}
На основе Kaz Yoshikawa ответьте
xCode 9.1, Swift 4
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"
}
}
Массив расширения
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)
}
}
// 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))")
}
Не забудьте добавить здесь код решения
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")
}
}
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
}
Здесь, как сделать отличный ответ @GoZoner, соответствует Hashable
, поэтому его можно индексировать в объектах Container, таких как: Set
, Dictionary
, Array
и т.д.
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
}
Еще одно решение одной и той же проблемы... основное внимание в этом вопросе заключается в сохранении слабой ссылки на объект, но позволяющей также хранить структуру.
[Я не уверен, насколько это полезно, но для получения синтаксиса это заняло некоторое время]
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
Другие ответы охватывают угол генериков. Думаю, я бы разделил простой код, охватывающий угол 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
?
Это мое решение:
-
// 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)) }
}
}
Поскольку 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: 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
}
Пример использования:
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
и т.д. (Но мой проект требует только append
и forEach
поэтому у меня нет точного кода под рукой).
Я основал это на работе @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
Вы можете создать обертку вокруг Array
. Или используйте эту библиотеку https://github.com/NickRybalko/WeakPointerArray
let array = WeakPointerArray<AnyObject>()
Это безопасный тип.