Swift に弱参照の配列を保存したいと思います。配列自体は弱参照であってはなりません - その要素は弱参照であるべきです。NSPointerArray
Cocoaはこれのタイプセーフでないバージョンを提供していると思います。
20 に答える
次のように汎用ラッパーを作成します。
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 言語ではそのように定義された拡張機能が許可されていないと思います。
You can use the NSHashTable with weakObjectsHashTable. NSHashTable<ObjectType>.weakObjectsHashTable()
For Swift 3: NSHashTable<ObjectType>.weakObjects()
Available in OS X v10.5 and later.
Available in iOS 6.0 and later.
パーティーにはちょっと遅いですが、私のものを試してみてください。配列ではなくセットとして実装しました。
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 ではないからです。私の迅速なバージョンは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
}
}
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 Developer Forums で見つけました。
@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]
これを行うには、ウィーク ポインターを保持するラッパー オブジェクトを作成します。
struct WeakThing<T: AnyObject> {
weak var value: T?
init (value: T) {
self.value = value
}
}
そして、これらを配列で使用します
var weakThings = WeakThing<Foo>[]()
機能的なスタイルのラッパーはどうですか?
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 }
ジェネリックを使用して弱いコンテナーを作成するという同じ考えがありました。
その結果、次のラッパーを作成しました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)。
そのため、conforming を削除し、アサーションAnyObject
を使用してガードを追加しました。fatalError()
WeakContainer の既存の例は役に立ちますが、Lists や Dictionaries などの既存の迅速なコンテナーで弱参照を使用する場合には、実際には役に立ちません。
contains などの 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!
}
これにより、弱い参照の Dictionary を使用するなど、いくつかのクールなことを行うことができます。
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)
}
同じ問題に対するさらに別の解決策...これの焦点は、オブジェクトへの弱い参照を保存することですが、構造体も保存できるようにすることです。
[それがどれほど役立つかはわかりませんが、構文を正しく理解するのに時間がかかりました]
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>()
タイプセーフです。
他の回答は、ジェネリックの角度をカバーしています。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))
}
}
これは私の解決策です:
- WeakObjectSet は WeakObject を格納しており、取得していないため、割り当て解除時に配列をクリーンアップします。
- Set で重複要素が見つかった場合の致命的なエラーを解決します。
--
// 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)) }
}
}