ฉันจะประกาศการอ้างอิงที่อ่อนแอใน Swift ได้อย่างไร


179

ฉันต้องการจัดเก็บเอกสารอ้างอิงอ่อน ๆ ใน Swift อาร์เรย์ไม่ควรเป็นข้อมูลอ้างอิงที่อ่อนแอ - องค์ประกอบควรเป็น ฉันคิดว่า Cocoa NSPointerArrayนำเสนอรุ่นที่ไม่ปลอดภัยสำหรับประเภทนี้


1
สิ่งที่เกี่ยวกับการสร้างวัตถุคอนเทนเนอร์ที่อ้างอิงวัตถุอื่นอย่างอ่อนช้อยจากนั้นสร้างอาร์เรย์ของวัตถุเหล่านั้น (ถ้าคุณไม่ได้คำตอบที่ดีกว่า)
nielsbot

1
ทำไมคุณไม่ใช้ NSPointerArray?
บาสเตียน

@nielsbot นั่นเป็นวิธีแก้ปัญหา obj-c แบบเก่า :) หากต้องการทำให้ Swifty มันควรเป็นวัตถุทั่วไป! :) อย่างไรก็ตามปัญหาที่แท้จริงคือวิธีนำวัตถุออกจากอาร์เรย์เมื่อวัตถุที่ถูกอ้างอิงถูกจัดสรรคืน
Sulthan

2
ใช่ฉันต้องการบางอย่างที่มีประเภทที่กำหนดพารามิเตอร์แล้ว ฉันเดาว่าฉันสามารถสร้างเสื้อคลุมที่มีพารามิเตอร์รอบ ๆ NSPointerArray แต่ต้องการดูว่ามีทางเลือกอื่นหรือไม่
Bill

6
เช่นเดียวกับตัวเลือกอื่น NSHashTable มีอยู่ โดยพื้นฐานแล้วมันเป็น NSSet ที่ให้คุณสามารถระบุว่าควรจะอ้างอิงวัตถุที่มีอยู่อย่างไร
มิก MacCallum

คำตอบ:


154

สร้างเสื้อคลุมทั่วไปเป็น:

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คุณสามารถใช้อย่างใดอย่างหนึ่งหรือstructclass

นอกจากนี้เพื่อช่วยในการอ่านเนื้อหาของอาร์เรย์อีกครั้งคุณสามารถทำบางสิ่งตามบรรทัดต่อไปนี้:

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

การใช้งานAnyObjectด้านบนควรถูกแทนที่ด้วยT- แต่ฉันไม่คิดว่าภาษา Swift ปัจจุบันอนุญาตให้มีการกำหนดนามสกุลดังกล่าวได้


11
คุณจะลบวัตถุห่อหุ้มออกจากอาร์เรย์ได้อย่างไรเมื่อค่าของมันถูกจัดสรรคืน?
Sulthan

9
ใช่มันทำให้คอมไพเลอร์ขัดข้อง
GoZoner

5
กรุณาโพสต์รหัสปัญหาของคุณในคำถามใหม่; ไม่มีเหตุผลที่จะทำคำตอบของฉันเมื่อมันอาจเป็นรหัสของคุณ!
GoZoner

2
@EdGamble รหัสที่ให้มาทำงานตามที่เป็นอยู่ แต่ล้มเหลวหากคุณแทนที่คลาสStuffด้วยโปรโตคอล ดูคำถามที่เกี่ยวข้องนี้
Theo

2
โครงสร้างจะดีกว่าเนื่องจากจะถูกเก็บไว้ในสแต็กแทนที่จะต้องดึงข้อมูลฮีป
KPM

60

คุณสามารถใช้ NSHashTable กับ weakObjectsHashTable NSHashTable<ObjectType>.weakObjectsHashTable()

สำหรับ Swift 3: NSHashTable<ObjectType>.weakObjects()

การอ้างอิงคลาส NSHashTable

พร้อมใช้งานใน OS X v10.5 และใหม่กว่า

พร้อมใช้งานใน iOS 6.0 และใหม่กว่า


คำตอบที่ดีที่สุดและอย่าคาดเอวเวลาห่อหุ้ม!
Ramis

1
นี่เป็นวิธีที่ฉลาด แต่ก็เหมือนกับคำตอบของ GoZoner สิ่งนี้ไม่สามารถใช้ได้กับประเภทที่มีAnyแต่ไม่ใช่AnyObjectเช่นโปรโตคอล
Aaron Brager

@SteveWilford แต่โปรโตคอลสามารถดำเนินการโดยชั้นเรียนซึ่งจะทำให้มันเป็นประเภทการอ้างอิง
Aaron Brager

4
โปรโตคอลสามารถขยายคลาสและจากนั้นคุณสามารถใช้เป็นอ่อน (เช่นโพรโทคอล MyProtocol: คลาส)
Yasmin Tiomkin

1
ฉันได้รับข้อผิดพลาดคอมไพเลอร์ด้วยและMyProtocol: class NSHashTable<MyProtocol>.weakObjects()"'NSHashTable' ต้องการให้ 'MyProtocol' เป็นประเภทชั้นเรียน
Greg

14

มันค่อนข้างช้าสำหรับปาร์ตี้ แต่ลองของฉัน ฉันใช้งานเป็นชุดไม่ใช่ชุดข้อมูล

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 เพราะประเภทสตริงไม่ใช่ AnyType Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)รุ่นที่รวดเร็วของฉันคือ

รหัสสามารถคว้าได้จากส่วนสำคัญ 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 อยู่นอกเหนือบริบทนี้ แต่ฉันจะขอบคุณถ้ามีคนคิดว่าปัญหานี้เกิดขึ้น


ฉันใช้โซลูชันนี้สำหรับโครงการของฉัน เยี่ยมมาก! เพียงหนึ่งข้อเสนอแนะการแก้ปัญหานี้ดูเหมือนจะไม่เอาค่าจากภายในnil Setดังนั้นฉันได้เพิ่มreap()ฟังก์ชั่นที่กล่าวถึงในคำตอบยอดนิยมและทำให้แน่ใจว่าจะโทรreap()ทุกครั้งที่WeakObjectSetมีการเข้าถึง
gokeji

อืมรอด้วยเหตุผลบางอย่างสิ่งนี้ไม่ทำงานใน Swift 4 / iOS 11 ดูเหมือนว่าการอ้างอิงที่อ่อนแอจะไม่ได้รับการจัดสรรคืนทันทีเมื่อค่ากลายเป็นnilอีกต่อไป
gokeji

1
ฉันอัปเดตโค้ดเป็น Swift4 ดูคำตอบครึ่งหลัง ฉันดูเหมือน NSString มีปัญหาการจัดสรรคืนบางอย่าง แต่มันควรจะยังคงทำงานกับคลาสอ็อบเจ็กต์ที่คุณกำหนดเอง
Kaz Yoshikawa

ขอบคุณมากที่มองมัน @KazYoshikawa และอัปเดตคำตอบ! ฉันก็ตระหนักในภายหลังว่าคลาสแบบกำหนดเองนั้นใช้งานNSStringได้
gokeji

2
ฉันได้ทำประสบการณ์ที่ตัวชี้ที่ส่งคืนโดยUnsafeMutablePointer<T>(&object)สามารถเปลี่ยนแบบสุ่ม (เช่นเดียวกับwithUnsafePointer) ตอนนี้ผมใช้รุ่นที่ได้รับการสนับสนุนโดยNSHashTable: gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d
simonseyer

12

นี่ไม่ใช่ทางออกของฉัน ผมพบว่ามันเกี่ยวกับแอปเปิ้ลฟอรั่มสำหรับนักพัฒนา

@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]

1
แปลก แต่ไม่ทำงานกับ structs อีกต่อไป พูดEXC_BAD_ACCESSกับฉัน กับการเรียนทำงานได้ดี
Mente

6
Structs เป็นประเภทค่ามันไม่ควรทำงานกับมัน ความจริงที่ว่ามันล้มเหลวที่รันไทม์แทนที่จะเป็นข้อผิดพลาดในการรวบรวมเวลาเป็นข้อผิดพลาดของคอมไพเลอร์
David Goodine

10

คุณสามารถทำได้โดยการสร้างวัตถุห่อหุ้มเพื่อถือตัวชี้ที่อ่อนแอ

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

แล้วใช้สิ่งเหล่านี้ในอาร์เรย์

var weakThings = WeakThing<Foo>[]()

จะต้องมีclassการใช้weakvars
Bill

3
พูดว่าใคร รหัสข้างต้นใช้งานได้ดีสำหรับฉัน ข้อกำหนดเพียงอย่างเดียวคือวัตถุที่อ่อนแอจะต้องเป็นคลาสไม่ใช่วัตถุที่มีการอ้างอิงที่อ่อนแอ
Joshua Weinberg

ขอโทษ ฉันสาบานได้เลยว่าฉันเพิ่งได้รับข้อความคอมไพเลอร์ที่กล่าวว่า "ไม่สามารถใช้ตัวแปรอ่อนแอใน structs" คุณถูกต้อง - นั่นคอมไพล์
Bill

5
@JoshuaWeinberg จะเกิดอะไรขึ้นถ้า Foo เป็นโปรโตคอล
onmyway133

@ onmyway133 AFAIK หากโปรโตคอลถูกประกาศให้ใช้งานโดยคลาสเท่านั้น protocol Protocol : class { ... }
olejnjak

8

เสื้อคลุมสไตล์ฟังก์ชั่นวิธีการเกี่ยวกับ?

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 }

3
คำถามคือวิธีการสร้างอาร์เรย์ (หรือพูดชุด) ของวัตถุที่อ่อนแอ
David H

var array: [(x: Int, y: () -> T?)]ด้วยวิธีนี้คุณยังสามารถสร้างอาร์เรย์ที่มีหลายค่าเช่น สิ่งที่ฉันกำลังมองหา
jboi

1
@DavidH ฉันอัปเดตคำตอบเพื่อตอบคำถาม ฉันหวังว่านี่จะช่วยได้.
eonil

ฉันชอบวิธีนี้และฉันคิดว่ามันฉลาดมาก ฉันใช้งานคลาสโดยใช้กลยุทธ์นี้ ขอบคุณ!
Ale Ravasio

let values = Array(array1.map({ $0() })) partไม่มากเกินไปแน่ใจเกี่ยวกับ เนื่องจากนี่ไม่ใช่อาร์เรย์ของการปิดที่มีการอ้างอิงอ่อนค่าจะถูกเก็บไว้จนกว่าอาร์เรย์นี้จะถูกจัดสรรคืน หากฉันถูกต้องมันเป็นสิ่งสำคัญที่จะต้องทราบว่าคุณไม่ควรเก็บอาร์เรย์นี้ไว้เช่นself.items = Array(array1.map({ $0() }))นี้
Matic Oblak

7

ฉันมีความคิดเดียวกันในการสร้างภาชนะบรรจุที่อ่อนแอด้วยยาชื่อสามัญ
เป็นผลให้ฉันสร้างเสื้อคลุมสำหรับ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()ยืนยัน



6

รายละเอียด

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

ปัญหาของฉันกับตัวเลือกทั้งสอง (และอื่น ๆ อีกมากมาย) คืออาร์เรย์ประเภทนี้ไม่สามารถใช้ได้กับโปรโตคอล เช่นนี้จะไม่รวบรวม:protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
Matic Oblak

@MaticOblak แล้วการใช้ยาชื่อสามัญ? 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

แนวคิดคืออาร์เรย์นี้สามารถเก็บออบเจ็กต์ประเภทต่าง ๆ ที่ใช้โปรโตคอลคลาสเดียวกัน โดยการใช้สามัญคุณล็อคลงไปเป็นประเภทเดียว delegatesยกตัวอย่างเช่นคิดที่มีซิงเกิลที่ถืออาร์เรย์เช่น จากนั้นคุณจะมีตัวควบคุมมุมมองจำนวนหนึ่งที่ต้องการใช้ฟังก์ชันนี้ MyManager.delegates.append(self)คุณจะคาดหวังที่จะเรียก แต่ถ้าMyManagerถูกล็อคเป็นประเภททั่วไปบางอย่างนี่ก็ใช้งานไม่ได้มาก
Matic Oblak

@MaticOblak ตกลง ลองสิ่งนี้: 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

ตอนนี้คุณได้สูญเสียชิ้นส่วนทั่วไปไปกับอาเรย์ซึ่งสำคัญนิดหน่อย :) ฉันรู้สึกว่ามันใช้ไม่ได้ ข้อ จำกัด ของ Swift ในตอนนี้ ...
Matic Oblak

4

ตัวอย่างที่มีอยู่ของ WeakContainer มีประโยชน์ แต่ก็ไม่ได้ช่วยให้ใครใช้การอ้างอิงที่อ่อนแอในคอนเทนเนอร์ที่รวดเร็วที่มีอยู่เช่นรายการและพจนานุกรม

หากคุณต้องการใช้วิธีการแบบรายการเช่นมีอยู่ 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)
}

3

นี่คือวิธีการที่จะทำให้ @ ตอบที่ดีของ GoZoner สอดคล้องกับHashableเพื่อที่จะสามารถได้รับการจัดทำดัชนีในวัตถุคอนเทนเนอร์ที่ชอบ: 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
}

3

เนื่องจาก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 มาก หากคุณต้องการที่จะทำให้มันมากขึ้นอาร์เรย์เหมือนคุณยังสามารถดำเนินการ subscripting ให้มันSequenceTypeฯลฯ ( แต่โครงการของฉันเพียงต้องการappendและforEachดังนั้นผมจึงไม่ได้มีรหัสที่ถูกต้องในมือ)


2

อีกวิธีหนึ่งในการแก้ปัญหาเดียวกัน ... ความสำคัญของสิ่งนี้คือการจัดเก็บการอ้างอิงที่อ่อนแอไปยังวัตถุ แต่ช่วยให้คุณสามารถจัดเก็บโครงสร้างได้เช่นกัน

[ฉันไม่แน่ใจว่ามันมีประโยชน์อย่างไร แต่ใช้เวลาสักครู่เพื่อให้ได้รูปแบบที่ถูกต้อง]

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


1

คำตอบอื่น ๆ ได้ครอบคลุมมุมทั่วไป คิดว่าฉันจะแบ่งปันรหัสง่ายๆที่ครอบคลุม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คืออะไร
Lukas Kubanek

0

ฉันใช้สิ่งนี้กับงานของ @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

0

นี่เป็นทางออกของฉัน:

  • ล้างข้อมูลอาร์เรย์เมื่อยกเลิกการจัดสรรเนื่องจาก WeakObjectSet กำลังจัดเก็บและไม่ได้รับการปิด WeakObject
  • แก้ไขข้อผิดพลาดร้ายแรงเมื่อองค์ประกอบที่ซ้ำกันที่พบในชุด

-

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

0

นี่เป็นคอลเลกชันที่ปลอดภัยประเภทที่มีภาชนะบรรจุวัตถุที่อ่อนแอ นอกจากนี้มันยังลบอัตโนมัติไม่มีศูนย์ภาชนะ / ห่อเมื่อมีการเข้าถึง

ตัวอย่าง:

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

คอลเล็กชันที่กำหนดเอง 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 }
    }
}

0

สิ่งที่เกี่ยวกับวิธีการทำงานหรือไม่

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

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

นี่เป็นแนวคิดหลักแล้วเพิ่มตรรกะความสะดวกสบายใด ๆ เพื่อติดตามสิ่งที่อยู่ในอาร์เรย์ ตัวอย่างเช่นเราสามารถพิจารณาวิธีการเดียวกันกับพจนานุกรมโดยใช้คีย์เป็นวิธีค้นหาสิ่งที่อยู่ในนั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.