Как объявить массив слабых ссылок в Swift?
Я хотел бы сохранить массив слабых ссылок в Swift. Сам массив не должен быть слабой ссылкой - его элементы должны быть. Я думаю, какао NSPointerArray
предлагает не-typesafe версию этого.
15 ответов
создайте общую оболочку как:
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 != .value }
}
}
использование AnyObject
выше следует заменить T
- но я не думаю, что текущий язык Swift позволяет расширение, определенное как таковое.
Вы можете использовать NSHashTable с weakObjectsHashTable. NSHashTable.weakObjectsHashTable()
Для Swift 3: NSHashTable.weakObjects()
доступно в OS X v10.5 и позже.
доступно в iOS 6.0 и более поздних версиях.
Это не мое решение. Я нашел его на форумах разработчиков 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]
поздновато, но попробую. Я реализовал как набор не массив.
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: ) })
}
var allObjects: [T] {
return objects.flatMap { .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: ) })
}
}
использование
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 не будет принимать строковый тип, но NSString. Потому что String type не является AnyType. Моя версия swift Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)
.
код можно схватить из Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39
** ДОБАВЛЕНО В НОЯБРЬ.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: ) })
}
var allObjects: [T] {
return objects.flatMap { .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: ) })
}
}
как упоминал гекедзи, я понял, что NSString не будет освобожден на основе кода в использовании. Я почесал в затылке и записал свой урок следующим образом.
// 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 выходят за рамки этого контекста, но я был бы признателен, если бы кто-то понял эту проблему.
вы можете сделать это, создав объект-оболочку для удержания слабого указателя.
struct WeakThing<T: AnyObject> {
weak var value: T?
init (value: T) {
self.value = value
}
}
а затем использовать их в массиве
var weakThings = WeakThing<Foo>[]()
у меня была такая же идея создать слабый контейнер с дженериками.
В результате я создал оболочку для 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({ () }))
существующий пример WeakContainer полезен, но это действительно не помогает использовать слабые ссылки в существующих контейнерах swift, таких как списки и словари.
Если вы хотите использовать методы списка, такие как contains, то 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)
}
вот как сделать отличный ответ @GoZoner в соответствии с Hashable
, поэтому его можно индексировать в объектах контейнера, таких как: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
}
на основе Каз Есикава ответ
подробности
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
}
результат
другие ответы покрыли угол дженериков. Думал, что поделюсь простым кодом, охватывающим nil
угол.
Я хотел статический массив (читал иногда) всех Label
s, которые в настоящее время существуют в приложении, но не хотят видеть 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{.label != nil}
return _allLabels.filter{.label != nil}.map{.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))
}
}
еще одно решение той же проблемы... фокус этого заключается в сохранении слабой ссылки на объект, но позволяет хранить структуру тоже.
[Я не уверен, насколько это полезно, но потребовалось некоторое время, чтобы получить правильный синтаксис]
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
Вы можете создать обертку вокруг Array
. Или используйте эту библиотеку https://github.com/NickRybalko/WeakPointerArray
let array = WeakPointerArray<AnyObject>()
Это тип safe.
я основывал это на работе @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
С NSPointerArray
уже обрабатывает большую часть этого автоматически, я решил проблему, сделав для нее типобезопасную обертку, что позволяет избежать многих шаблонов в других ответах:
class WeakArray<T: AnyObject> {
private let pointers = NSPointerArray.weakObjects()
init (_ elements: T...) {
elements.forEach{self.pointers.addPointer(Unmanaged.passUnretained().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
, etc. (но моему проекту нужно только append
и forEach
так что я нет точного кода под рукой).