Bagaimana cara mendeklarasikan serangkaian referensi lemah di Swift?

179

Saya ingin menyimpan sejumlah referensi lemah di Swift. Array itu sendiri seharusnya tidak menjadi referensi yang lemah - elemen-elemennya seharusnya. Saya pikir Cocoa NSPointerArraymenawarkan versi yang tidak aman untuk ini.

Tagihan
sumber
1
Bagaimana dengan membuat objek kontainer yang mereferensikan objek lain dengan lemah, lalu membuat arraynya? (Jika Anda tidak mendapatkan jawaban yang lebih baik)
nielsbot
1
mengapa Anda tidak menggunakan NSPointerArray?
Bastian
@nielsbot Itu solusi obj-c lama :) Untuk membuatnya Swifty, itu harus menjadi objek generik! :) Namun, masalah sebenarnya adalah bagaimana cara menghapus objek dari array ketika objek yang direferensikan dialokasikan.
Sulthan
2
Benar, saya lebih suka sesuatu dengan tipe parameter. Saya kira saya bisa membuat pembungkus parameterized di sekitar NSPointerArray, tetapi ingin melihat apakah ada alternatif.
Bill
6
Sama seperti opsi lain, NSHashTable ada. Ini pada dasarnya NSSet yang memungkinkan Anda menentukan bagaimana seharusnya referensi objek yang dikandungnya.
Mick MacCallum

Jawaban:

154

Buat pembungkus generik sebagai:

class Weak<T: AnyObject> {
  weak var value : T?
  init (value: T) {
    self.value = value
  }
}

Tambahkan instance kelas ini ke array Anda.

class Stuff {}
var weakly : [Weak<Stuff>] = [Weak(value: Stuff()), Weak(value: Stuff())]

Saat mendefinisikan, WeakAnda dapat menggunakan salah satu structatau class.

Juga, untuk membantu menuai konten array, Anda dapat melakukan sesuatu di sepanjang baris:

extension Array where Element:Weak<AnyObject> {
  mutating func reap () {
    self = self.filter { nil != $0.value }
  }
}

Penggunaan di AnyObjectatas harus diganti dengan T- tapi saya tidak berpikir bahasa Swift saat ini memungkinkan ekstensi yang ditentukan.

GoZoner
sumber
11
Bagaimana Anda menghapus objek pembungkus dari array ketika nilainya dibatalkan alokasi?
Sulthan
9
Ya, itu menabrak kompiler.
GoZoner
5
Silakan kirim kode masalah Anda dalam pertanyaan baru; tidak ada alasan untuk menjawab jawaban saya ketika itu mungkin kode Anda!
GoZoner
2
@ EdGamble Kode yang disediakan berfungsi sebagaimana adanya, tetapi gagal jika Anda mengganti kelas Stuffdengan protokol; lihat pertanyaan terkait ini
Theo
2
Sebuah struct akan lebih baik, karena akan disimpan di stack daripada perlu mengambil heap.
KPM
60

Anda dapat menggunakan NSHashTable dengan lemahObjectsHashTable. NSHashTable<ObjectType>.weakObjectsHashTable()

Untuk Swift 3: NSHashTable<ObjectType>.weakObjects()

Referensi Kelas NSHashTable

Tersedia dalam OS X v10.5 dan yang lebih baru.

Tersedia di iOS 6.0 dan yang lebih baru.

Thierry
sumber
Jawaban terbaik dan jangan waktu pinggang untuk pembungkus!
Ramis
1
Ini cerdas, tetapi seperti jawaban GoZoner, ini tidak bekerja dengan tipe yang Anytetapi tidak AnyObject, seperti protokol.
Aaron Brager
@SteveWilford Tapi protokol dapat diimplementasikan oleh kelas, yang akan membuatnya menjadi tipe referensi
Aaron Brager
4
protokol dapat memperluas kelas dan kemudian Anda dapat menggunakannya sebagai lemah (mis. protokol MyProtocol: class)
Yasmin Tiomkin
1
Saya mendapatkan kesalahan kompilator dengan MyProtocol: classdan NSHashTable<MyProtocol>.weakObjects(). "'NSHashTable' mengharuskan 'MyProtocol' menjadi tipe kelas.
Greg
14

Agak terlambat untuk pesta, tapi coba milikku. Saya menerapkan sebagai Set bukan Array.

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) })
    }
}

Pemakaian

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]

Berhati-hatilah bahwa WeakObjectSet tidak akan mengambil tipe String selain NSString. Karena, tipe String bukan jenis apa pun. Versi cepat saya adalah Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

Kode dapat diambil dari Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** DITAMBAH DALAM NOVEMBER.2017

Saya memperbarui kode ke 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) })
    }
}

Seperti yang disebutkan gokeji, saya tahu NSString tidak akan dibatalkan alokasi berdasarkan kode yang digunakan. Saya menggaruk kepala dan menulis kelas MyString sebagai berikut.

// 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
    }
}

Lalu gantikan NSStringdengan MyStringseperti ini. Kemudian aneh untuk mengatakan itu berhasil.

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]

Kemudian saya menemukan halaman aneh yang mungkin terkait dengan masalah ini.

Referensi yang lemah mempertahankan NSString yang tidak dapat dialokasikan (hanya XC9 + iOS Sim)

https://bugs.swift.org/browse/SR-5511

Dikatakan masalahnya RESOLVEDtetapi saya bertanya-tanya apakah ini masih terkait dengan masalah ini. Bagaimanapun, perbedaan Perilaku antara MyString atau NSString berada di luar konteks ini, tapi saya akan sangat menghargai jika seseorang menemukan masalah ini.

Kaz Yoshikawa
sumber
Saya telah mengadopsi solusi ini untuk proyek saya. Kerja bagus! Hanya satu saran, solusi ini sepertinya tidak menghilangkan nilnilai dari internal Set. Jadi saya telah menambahkan reap()fungsi yang disebutkan dalam jawaban atas, dan memastikan untuk menelepon reap()setiap kali WeakObjectSetdiakses.
gokeji
Hmm tunggu, untuk beberapa alasan ini tidak berfungsi di Swift 4 / iOS 11. Sepertinya referensi yang lemah tidak dapat langsung dialokasikan saat nilai menjadi nillagi
gokeji
1
Saya memperbarui kode ke Swift4, lihat bagian kedua jawabannya. Saya tampaknya NSString memiliki beberapa masalah deallokasi, tetapi masih harus bekerja pada objek kelas kustom Anda.
Kaz Yoshikawa
Terima kasih banyak telah melihatnya @KazYoshikawa, dan memperbarui jawabannya! Saya juga menyadari kemudian bahwa kelas khusus berfungsi, sedangkan NSStringtidak.
gokeji
2
Saya telah membuat pengalaman bahwa pointer yang dikembalikan oleh UnsafeMutablePointer<T>(&object)dapat berubah secara acak (sama dengan withUnsafePointer). Saya sekarang menggunakan versi yang didukung oleh NSHashTable: gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d .
simonseyer
12

Ini bukan solusi saya. Saya menemukannya di Forum Pengembang Apple .

@GoZoner memiliki jawaban yang bagus, tetapi itu merusak kompiler Swift.

Berikut ini versi dari wadah objek lemah yang tidak merusak kompiler yang dirilis saat ini.

struct WeakContainer<T where T: AnyObject> {
    weak var _value : T?

    init (value: T) {
        _value = value
    }

    func get() -> T? {
        return _value
    }
}

Anda kemudian dapat membuat array dari kontainer ini:

let myArray: Array<WeakContainer<MyClass>> = [myObject1, myObject2]
rjkaplan
sumber
1
aneh, tetapi tidak bekerja dengan struct lagi. Mengatakan EXC_BAD_ACCESSuntuk saya. Dengan kelas bekerja dengan baik
mente
6
Structs adalah tipe nilai, tidak seharusnya bekerja dengannya. Fakta bahwa crash pada saat runtime daripada kesalahan waktu kompilasi adalah bug kompilator.
David Goodine
10

Anda dapat melakukan ini dengan membuat objek pembungkus untuk memegang pointer lemah.

struct WeakThing<T: AnyObject> {
  weak var value: T?
  init (value: T) {
    self.value = value
  }
}

Dan kemudian menggunakan ini dalam array

var weakThings = WeakThing<Foo>[]()
Joshua Weinberg
sumber
Harus classmenggunakan weakvars
Bill
3
Kata siapa? Kode di atas berfungsi dengan baik untuk saya. Satu-satunya syarat adalah bahwa objek yang menjadi lemah harus berupa kelas, bukan objek yang memegang referensi yang lemah
Joshua Weinberg
Maaf. Saya bisa bersumpah saya baru saja mendapat pesan kompiler yang mengatakan "Tidak dapat menggunakan variabel lemah dalam struct". Anda benar - kompilasi itu.
Bill
5
@ JoshuaWeinberg bagaimana jika Foo adalah protokol?
onmyway133
@ onmyway133 AFAIK jika protokol dinyatakan hanya diimplementasikan oleh kelas itu akan bekerja. protocol Protocol : class { ... }
olejnjak
8

Bagaimana dengan pembungkus gaya fungsional?

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)

Panggil saja penutupan kembali untuk memeriksa target masih hidup.

let isAlive = captured1() != nil
let theValue = captured1()!

Dan Anda bisa menyimpan penutupan ini ke dalam array.

let array1 = Array<() -> (Class1?)>([captured1, captured2, captured3])

Dan Anda dapat mengambil nilai yang ditangkap dengan lemah dengan memetakan pemetaan penutupan.

let values = Array(array1.map({ $0() }))

Sebenarnya, Anda tidak perlu fungsi untuk membuat penutupan. Hanya menangkap objek secara langsung.

let captured3 = { [weak obj3] in return obj3 }
eonil
sumber
3
Pertanyaannya adalah bagaimana membuat array (atau mengatakan Set) dari objek yang lemah.
David H
Dengan solusi ini, Anda bahkan dapat membuat array dengan banyak nilai seperti var array: [(x: Int, y: () -> T?)]. Persis, apa yang saya cari.
jboi
1
@ David Saya memperbarui jawaban saya untuk menjawab pertanyaan. Saya harap ini membantu.
eonil
Saya menyukai pendekatan ini, dan saya pikir ini sangat pintar. Saya membuat implementasi kelas menggunakan strategi ini. Terima kasih!
Ale Ravasio
Tidak terlalu yakin tentang let values = Array(array1.map({ $0() })) part. Karena ini bukan lagi array penutupan dengan referensi yang lemah, nilai akan dipertahankan hingga array ini tidak dialokasikan. Jika saya benar maka penting untuk dicatat bahwa Anda tidak boleh mempertahankan array ini seperti self.items = Array(array1.map({ $0() }))ini mengalahkan tujuannya.
Matic Oblak
7

Saya memiliki ide yang sama untuk membuat wadah yang lemah dengan obat generik.
Sebagai hasilnya saya membuat pembungkus untuk 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?
        }
    }
}

Pemakaian:

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()
}

Ini bukan solusi terbaik, karena WeakSetdapat diinisialisasi dengan jenis apa pun, dan jika jenis ini tidak sesuai dengan AnyObjectprotokol maka aplikasi akan macet dengan alasan terperinci. Tapi saya tidak melihat solusi yang lebih baik saat ini.

Solusi asli adalah mendefinisikan WeakSetdengan cara ini:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

Tetapi dalam kasus WeakSetini tidak dapat diinisialisasi dengan protokol:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

Saat ini kode di atas tidak dapat dikompilasi (Swift 2.1, Xcode 7.1).
Itu sebabnya saya turun menyesuaikan diri AnyObjectdan menambahkan penjaga tambahan dengan fatalError()penegasan.

Vlad Papko
sumber
Huh gunakan untuk objek di hashtable.allObjects
malhal
6

Detail

  • Swift 5.1, Xcode 11.3.1

Larutan

struct WeakObject<Object: AnyObject> { weak var object: Object? }

Pilihan 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) }
    }
}

Opsi 1 penggunaan

class Class1 { // or struct
    @WeakElements var weakObjectsArray = [UIView?]() // Use like regular array. With any objects

    func test() {
        weakObjectsArray.append(UIView())
        weakObjectsArray.forEach { print($0) }
    }
}

pilihan 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) })
    }
}

Opsi 2 penggunaan

class Class2 { // or struct
    var weakObjectsArray = WeakObjectsArray<UIView>() // Use like regular array. With any objects

    func test() {
        weakObjectsArray.append(UIView())
        weakObjectsArray.forEach { print($0) }
    }
}

Sampel lengkap

jangan lupa tempel kode solusi

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")
    }
}
Bodnarchuk dengan mudah
sumber
Masalah saya dengan kedua opsi (dan banyak lainnya) adalah bahwa jenis array ini tidak dapat digunakan dengan protokol. Misalnya ini tidak akan dikompilasi:protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
Matic Oblak
@MaticOblak bagaimana dengan menggunakan obat generik? protocol TP: class { } class TC<TYPE> where TYPE: TP { var a = WeakObjectsArray<TYPE>() // Use like regular array. With any objects var weakObjectsArray = [TYPE?]() }
Vasily Bodnarchuk
Idenya adalah bahwa array ini dapat menampung objek dari tipe berbeda yang mengimplementasikan protokol kelas yang sama. Dengan menggunakan generik Anda menguncinya ke satu jenis. Misalnya bayangkan memiliki singleton yang memiliki array seperti delegates. Maka Anda akan memiliki beberapa pengontrol tampilan yang ingin menggunakan fungsi ini. Anda akan menelepon MyManager.delegates.append(self). Tetapi jika MyManagerdikunci ke beberapa jenis generik maka ini tidak terlalu berguna.
Matic Oblak
@MaticOblak ok. Coba ini: protocol 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
Anda sekarang kehilangan bagian generik dengan array yang sedikit penting :) Saya punya perasaan bahwa ini tidak bisa dilakukan. Batasan Swift untuk saat ini ...
Matic Oblak
4

Contoh WeakContainer yang ada sangat membantu, tetapi itu tidak benar-benar membantu seseorang menggunakan referensi yang lemah dalam wadah cepat yang ada seperti Daftar dan Kamus.

Jika Anda ingin menggunakan metode Daftar seperti berisi, maka WeakContainer perlu mengimplementasikan Equatable. Jadi saya menambahkan kode untuk memungkinkan WeakContainer menjadi setara.

Jika Anda ingin menggunakan WeakContainer dalam kamus, saya juga membuatnya menjadi hashable sehingga dapat digunakan sebagai kunci kamus.

Saya juga menamainya menjadi WeakObject untuk menekankan bahwa ini hanya untuk tipe kelas dan untuk membedakannya dari contoh 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!
}

Ini memungkinkan Anda melakukan beberapa hal keren seperti menggunakan Kamus referensi lemah:

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)
}
Tod Cunningham
sumber
3

Berikut adalah cara untuk membuat @ jawaban yang bagus GoZoner ini sesuai dengan Hashable, sehingga dapat terindeks di Kontainer benda seperti: Set, Dictionary, Array, dll

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
}
Sakiboy
sumber
3

Karena NSPointerArraysudah menangani sebagian besar dari ini secara otomatis, saya memecahkan masalah dengan membuat pembungkus yang aman untuknya, yang menghindari banyak boilerplate dalam jawaban lain:

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
}

Contoh penggunaan:

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

Ini lebih berfungsi di muka, tetapi penggunaan di sisa kode Anda jauh lebih bersih IMO. Jika Anda ingin membuatnya lebih seperti array, Anda bahkan dapat mengimplementasikan subskript, membuatnya menjadi SequenceType, dll. (Tetapi proyek saya hanya perlu appenddan forEachjadi saya tidak memiliki kode yang tepat).

John Montgomery
sumber
2

Namun solusi lain untuk masalah yang sama ... fokus yang satu ini adalah menyimpan referensi yang lemah ke suatu objek tetapi memungkinkan Anda untuk menyimpan struct juga.

[Saya tidak yakin seberapa berguna itu, tetapi butuh beberapa saat untuk mendapatkan sintaks yang benar]

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
Dan Rosenstark
sumber
1

Jawaban lain telah membahas sudut generik. Kupikir aku akan membagikan beberapa kode sederhana yang menutupi nilsudut.

Saya ingin array statis (baca sesekali) dari semua Labelyang saat ini ada di aplikasi, tetapi tidak ingin melihat nildi mana yang lama dulu.

Tidak ada yang mewah, ini kode saya ...

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))
    }
}
wils
sumber
Bagaimana dengan menggunakan flatMapalih-alih filter& map?
Lukas Kubanek
0

Saya mendasarkan ini pada pekerjaan @Eonil, karena saya menyukai strategi penutupan yang lemah, tetapi saya tidak ingin menggunakan operator fungsi untuk suatu variabel, karena rasanya sangat berlawanan dengan intuisi

Apa yang saya lakukan adalah sebagai berikut:

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
        }
    }
}

Dengan cara ini Anda dapat melakukan sesuatu seperti:

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
Ale Ravasio
sumber
0

Ini solusi saya:

  • Bersihkan array ketika dideallocated, karena WeakObjectSet menyimpan dan tidak turun WeakObject
  • Mengatasi kesalahan fatal ketika elemen duplikat ditemukan di 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)) }
    }
}
YannSteph
sumber
0

Ini adalah jenis koleksi aman yang menampung wadah benda lemah. Itu juga otomatis menghapus nil wadah / pembungkus ketika diakses.

Contoh:

protocol SomeDelegate: class {
    func doSomething()
}

class SomeViewController: UIViewController {
    var delegates: WeakCollection<SomeDelegate> = []

    func someFunction(delegate: SomeDelegate) {
        delegates.append(delegate)
    }

    func runDelegates() {
        delegates.forEach { $0.doSomething() }
    }
}

Koleksi khusus https://gist.github.com/djk12587/46d85017fb3fad6946046925f36cefdc

import Foundation

/**
 Creates an array of weak reference objects.
 - Important:
    Because this is an array of weak objects, the objects in the array can be removed at any time.
    The collection itself will handle removing nil objects (garbage collection) via the private function cleanUpNilContainers()
 */

class WeakCollection<T>: RangeReplaceableCollection, ExpressibleByArrayLiteral {
    typealias Index = Int
    typealias Element = T
    typealias Iterator = IndexingIterator<[Element]>

    private var weakContainers: [WeakReferenceContainer]

    required convenience init(arrayLiteral: Element...) {
        self.init()
        self.weakContainers = WeakCollection.createWeakContainers(from: arrayLiteral)
    }

    required init() {
        weakContainers = []
    }

    required init<S>(_ elements: S) where S: Sequence, WeakCollection.Element == S.Element {
        self.weakContainers = WeakCollection.createWeakContainers(from: elements)
    }

    static private func createWeakContainers<S>(from weakCollection: S) -> [WeakReferenceContainer] where S: Sequence,
        WeakCollection.Element == S.Element {
            return weakCollection.compactMap { WeakReferenceContainer(value: $0 as AnyObject) }
    }

    func append<S>(contentsOf newElements: S) where S: Sequence, WeakCollection.Element == S.Element {
        self.weakContainers.append(contentsOf: WeakCollection.createWeakContainers(from: newElements))
    }

    var startIndex: Index {
        return references.startIndex
    }

    var endIndex: Index {
        return references.endIndex
    }

    func replaceSubrange<C, R>(_ subrange: R, with newElements: C) where
        C: Collection, R: RangeExpression, WeakCollection.Element == C.Element, WeakCollection.Index == R.Bound {
            weakContainers.replaceSubrange(subrange, with: WeakCollection.createWeakContainers(from: newElements))
    }

    func index(after i: Int) -> Int {
        return references.index(after: i)
    }

    func makeIterator() -> IndexingIterator<[Element]> {
        return references.makeIterator()
    }

    subscript(index: Int) -> Element {
        get {
            return references[index]
        }
        set {
            weakContainers[index] = WeakReferenceContainer(value: newValue as AnyObject)
        }
    }
}

extension WeakCollection {
    private class WeakReferenceContainer {
        private(set) weak var value: AnyObject?

        init(value: AnyObject?) {
            self.value = value
        }
    }

    private func cleanUpNilContainers() {
        weakContainers = weakContainers.compactMap { $0.value == nil ? nil : $0 }
    }

    private var references: [Element] {
        cleanUpNilContainers()
        return weakContainers.compactMap { $0.value as? T }
    }
}
Dan
sumber
0

Bagaimana dengan pendekatan fungsional ?

let observers = [() -> Observer?]()

observers.append({ [weak anObserver] in return anObserver })

Ini adalah ide utama, lalu tambahkan logika kenyamanan apa pun untuk melacak apa yang ada dalam array. Misalnya, seseorang dapat mempertimbangkan pendekatan yang sama dengan Kamus menggunakan kunci sebagai cara menemukan apa yang ada di sana.

kemari
sumber