Làm cách nào để khai báo một loạt các tham chiếu yếu trong Swift?


179

Tôi muốn lưu trữ một loạt các tài liệu tham khảo yếu trong Swift. Bản thân mảng không nên là một tham chiếu yếu - các phần tử của nó phải là. Tôi nghĩ rằng Cacao NSPointerArraycung cấp một phiên bản không an toàn về điều này.


1
Điều gì về việc tạo một đối tượng container tham chiếu yếu đối tượng khác, sau đó tạo một mảng của những đối tượng đó? (Nếu bạn không nhận được câu trả lời tốt hơn)
nielsbot

1
Tại sao bạn không sử dụng NSPulumArray?
Bastian

@nielsbot Đó là một giải pháp obj-c cũ :) Để làm cho nó Swifty, nó phải là một đối tượng chung! :) Tuy nhiên, vấn đề thực sự là làm thế nào để loại bỏ các đối tượng khỏi mảng khi đối tượng được tham chiếu bị hủy.
Sulthan

2
Phải, tôi thích cái gì đó với các loại tham số. Tôi đoán rằng tôi có thể tạo một trình bao bọc được tham số hóa xung quanh NSPulumArray, nhưng muốn xem liệu có bất kỳ lựa chọn thay thế nào không.
Bill

6
Cũng như một tùy chọn khác, NSHashTable tồn tại. Về cơ bản, nó là một NSSet cho phép bạn chỉ định cách nó sẽ tham chiếu các đối tượng mà nó chứa.
Mick MacCallum

Câu trả lời:


154

Tạo một trình bao bọc chung như:

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

Thêm các thể hiện của lớp này vào mảng của bạn.

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

Khi xác định Weakbạn có thể sử dụng structhoặc class.

Ngoài ra, để giúp gặt hái nội dung mảng, bạn có thể làm gì đó dọc theo dòng:

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

Việc sử dụng AnyObjectở trên nên được thay thế bằng T- nhưng tôi không nghĩ rằng ngôn ngữ Swift hiện tại cho phép một tiện ích mở rộng được xác định như vậy.


11
Làm thế nào để bạn loại bỏ các đối tượng trình bao bọc khỏi mảng khi giá trị của chúng được giải phóng?
Sulthan

9
Vâng, nó bị lỗi trình biên dịch.
GoZoner

5
Vui lòng gửi mã vấn đề của bạn trong một câu hỏi mới; không có lý do để ding câu trả lời của tôi khi nó có thể là mã của bạn!
GoZoner

2
@EdGamble Mã được cung cấp hoạt động như vậy, nhưng không thành công nếu bạn thay thế lớp Stuffbằng một giao thức; xem câu hỏi liên quan này
Theo

2
Một cấu trúc sẽ tốt hơn, vì nó sẽ được giữ trên ngăn xếp thay vì cần tìm nạp heap.
KPM

60

Bạn có thể sử dụng NSHashTable với yếuObjectsHashTable. NSHashTable<ObjectType>.weakObjectsHashTable()

Dành cho Swift 3: NSHashTable<ObjectType>.weakObjects()

Tham khảo lớp NSHashTable

Có sẵn trong OS X v10.5 trở lên.

Có sẵn trong iOS 6.0 trở lên.


Câu trả lời tốt nhất và không eo thời gian cho giấy gói!
Ramis

1
Điều này thật thông minh, nhưng giống như câu trả lời của GoZon, điều này không hoạt động với các loại Anynhưng không AnyObject, chẳng hạn như các giao thức.
Aaron Brager

@SteveWilford Nhưng một giao thức có thể được thực hiện bởi một lớp, điều này sẽ biến nó thành một kiểu tham chiếu
Aaron Brager

4
một giao thức có thể mở rộng lớp và sau đó bạn có thể sử dụng nó ở mức yếu (ví dụ: giao thức MyProtocol: class)
Yasmin Tiomkin

1
Tôi nhận được một lỗi biên dịch với MyProtocol: classNSHashTable<MyProtocol>.weakObjects(). "'NSHashTable' yêu cầu 'MyProtocol' phải là loại lớp.
Greg

14

Đó là loại muộn của bữa tiệc, nhưng hãy thử của tôi. Tôi đã triển khai như một Set không phải là một mảng.

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

Sử dụng

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]

Coi chừng WeakObjectSet sẽ không lấy loại Chuỗi nhưng NSString. Bởi vì, kiểu String không phải là AnyType. Phiên bản nhanh chóng của tôi là Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

Mã có thể được lấy từ Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** THÊM VÀO NOV.2017

Tôi đã cập nhật mã lên 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) })
    }
}

Như gokeji đã đề cập, tôi đã tìm ra NSString sẽ không bị xử lý dựa trên mã sử dụng. Tôi gãi đầu và tôi đã viết lớp MyString như sau.

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

Sau đó thay thế NSStringbằng MyStringnhư thế này. Sau đó, lạ để nói rằng nó hoạt động.

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]

Sau đó, tôi tìm thấy một trang lạ có thể liên quan đến vấn đề này.

Tham chiếu yếu giữ lại NSString đã phân bổ (chỉ dành cho Sim XC9 + iOS)

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

Nó nói vấn đề là RESOLVEDnhưng tôi tự hỏi nếu điều này vẫn còn liên quan đến vấn đề này. Dù sao, sự khác biệt về hành vi giữa MyString hoặc NSString nằm ngoài bối cảnh này, nhưng tôi sẽ đánh giá cao nếu ai đó phát hiện ra vấn đề này.


Tôi đã áp dụng giải pháp này cho dự án của mình. Bạn đã làm rất tốt! Chỉ cần một đề xuất, giải pháp này dường như không xóa nilgiá trị khỏi nội bộ Set. Vì vậy, tôi đã thêm một reap()chức năng được đề cập trong câu trả lời hàng đầu và đảm bảo gọi reap()mỗi khi WeakObjectSettruy cập.
gokeji

Đợi đã, vì một số lý do, điều này không hoạt động trong Swift 4 / iOS 11. Có vẻ như tham chiếu yếu không được xử lý ngay lập tức khi giá trị trở nên nilnữa
gokeji

1
Tôi đã cập nhật mã lên Swift4, xem nửa sau của câu trả lời. Tôi có vẻ như NSString có một số vấn đề đối phó, nhưng nó vẫn hoạt động trên các đối tượng lớp tùy chỉnh của bạn.
Kaz Yoshikawa

Cảm ơn rất nhiều vì đã xem nó @KazYoshikawa và cập nhật câu trả lời! Sau đó tôi cũng nhận ra rằng một lớp tùy chỉnh hoạt động, trong khi NSStringkhông.
gokeji

2
Tôi đã có kinh nghiệm rằng con trỏ được trả về UnsafeMutablePointer<T>(&object)có thể thay đổi ngẫu nhiên (giống với withUnsafePointer). Bây giờ tôi sử dụng một phiên bản được hỗ trợ bởiNSHashTable : gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d .
simonseyer

12

Đây không phải là giải pháp của tôi. Tôi đã tìm thấy nó trên Diễn đàn nhà phát triển của Apple .

@GoZoner có một câu trả lời hay, nhưng nó làm sập trình biên dịch Swift.

Đây là phiên bản của bộ chứa đối tượng yếu không làm sập trình biên dịch được phát hành hiện tại.

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

    init (value: T) {
        _value = value
    }

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

Sau đó, bạn có thể tạo một mảng của các thùng chứa này:

let myArray: Array<WeakContainer<MyClass>> = [myObject1, myObject2]

1
lạ, nhưng không hoạt động với structs nữa. Nói EXC_BAD_ACCESScho tôi. Với lớp học hoạt động tốt
mente

6
Cấu trúc là loại giá trị, nó không nên hoạt động với chúng. Thực tế là nó bị lỗi trong thời gian chạy chứ không phải là lỗi thời gian biên dịch là lỗi trình biên dịch.
David Goodine

10

Bạn có thể làm điều này bằng cách tạo một đối tượng trình bao bọc để giữ một con trỏ yếu.

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

Và sau đó sử dụng chúng trong mảng

var weakThings = WeakThing<Foo>[]()

Phải là một người classsử dụng weakvars
Bill

3
Nói ai? Các mã trên hoạt động tốt cho tôi. Yêu cầu duy nhất là đối tượng trở nên yếu cần phải là một lớp chứ không phải đối tượng giữ tham chiếu yếu
Joshua Weinberg

Lấy làm tiếc. Tôi có thể đã thề rằng tôi vừa nhận được một thông báo trình biên dịch có nội dung "Không thể sử dụng các biến yếu trong các cấu trúc". Bạn đã đúng - biên dịch.
Bill

5
@JoshuaWeinberg nếu Foo là một giao thức thì sao?
onmyway133

@ onmyway133 AFAIK nếu giao thức được khai báo chỉ được triển khai bởi các lớp thì nó sẽ hoạt động. protocol Protocol : class { ... }
olejnjak

8

Làm thế nào về phong cách chức năng bao bọc?

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)

Chỉ cần gọi đóng lại để kiểm tra mục tiêu vẫn còn sống.

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

Và bạn có thể lưu trữ đóng cửa này thành một mảng.

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

Và bạn có thể truy xuất các giá trị bị bắt yếu bằng cách ánh xạ gọi các bao đóng.

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

Trên thực tế, bạn không cần một chức năng để đóng cửa. Chỉ cần chụp một đối tượng trực tiếp.

let captured3 = { [weak obj3] in return obj3 }

3
Câu hỏi là làm thế nào để tạo một mảng (hoặc nói Set) của các đối tượng yếu.
David H

Với giải pháp này, bạn thậm chí có thể tạo một mảng với nhiều giá trị như thế nào var array: [(x: Int, y: () -> T?)]. Chính xác những gì tôi đang tìm kiếm.
jboi

1
@DavidH Tôi đã cập nhật câu trả lời của mình để trả lời câu hỏi. Tôi hi vọng cái này giúp được.
eonil

Tôi thích cách tiếp cận này và tôi nghĩ nó siêu thông minh. Tôi đã thực hiện một lớp học bằng cách sử dụng chiến lược này. Cảm ơn bạn!
Ale Ravasio

Không quá chắc chắn về let values = Array(array1.map({ $0() })) part. Vì đây không còn là một mảng các bao đóng với các tham chiếu yếu, các giá trị sẽ được giữ lại cho đến khi mảng này được giải phóng. Nếu tôi đúng thì điều quan trọng cần lưu ý là bạn không bao giờ nên giữ lại mảng self.items = Array(array1.map({ $0() }))này như điều này đánh bại mục đích.
Matic Oblak

7

Tôi đã có cùng một ý tưởng để tạo ra container yếu với thuốc generic.
Kết quả là tôi đã tạo trình bao bọc cho 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?
        }
    }
}

Sử dụng:

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

Đây không phải là giải pháp tốt nhất, bởi vì WeakSetcó thể được khởi tạo với bất kỳ loại nào và nếu loại này không tuân thủ AnyObjectgiao thức thì ứng dụng sẽ bị sập với lý do chi tiết. Nhưng tôi không thấy bất kỳ giải pháp tốt hơn ngay bây giờ.

Giải pháp ban đầu là xác định WeakSettheo cách này:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

Nhưng trong trường hợp này WeakSetkhông thể khởi tạo với giao thức:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

Hiện tại mã trên không thể được biên dịch (Swift 2.1, Xcode 7.1).
Đó là lý do tại sao tôi bỏ việc tuân thủ AnyObjectvà thêm các vệ sĩ bổ sung với các fatalError()xác nhận.


Huh chỉ sử dụng cho đối tượng trong hashtable.allObjects
malhal

6

Chi tiết

  • Swift 5.1, Xcode 11.3.1

Giải pháp

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

lựa chọn 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) }
    }
}

Cách sử dụng 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) }
    }
}

Lựa chọn 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) })
    }
}

Cách sử dụng 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) }
    }
}

Mẫu đầy đủ

đừng quên dán mã giải pháp

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

Vấn đề của tôi với cả hai tùy chọn (và nhiều tùy chọn khác) là các loại mảng này không thể sử dụng được với các giao thức. Chẳng hạn, điều này sẽ không được biên dịch:protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
Matic Oblak

@MaticOblak còn việc sử dụng thuốc generic thì sao? 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

Ý tưởng là mảng này có thể chứa các đối tượng thuộc các loại khác nhau thực hiện cùng một giao thức lớp. Bằng cách sử dụng chung chung, bạn khóa nó thành một loại duy nhất. Ví dụ, hãy tưởng tượng có một singleton chứa mảng như vậy delegates. Sau đó, bạn sẽ có một số bộ điều khiển xem muốn sử dụng chức năng này. Bạn sẽ gọi MyManager.delegates.append(self). Nhưng nếu MyManagerbị khóa với một số loại chung chung thì điều này không thể sử dụng được.
Matic Oblak

@MaticOblak ok. Hãy thử điều này: 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

Bây giờ bạn đã mất phần chung với mảng đó là một chút quan trọng :) Tôi có cảm giác rằng điều này là không thể thực hiện được. Một hạn chế của Swift bây giờ ...
Matic Oblak

4

Ví dụ hiện có của WeakContainer rất hữu ích, nhưng nó không thực sự giúp người ta sử dụng các tài liệu tham khảo yếu trong các thùng chứa nhanh như Danh sách và Từ điển.

Nếu bạn muốn sử dụng các phương thức List như chứa, thì WeakContainer sẽ cần triển khai Equitable. Vì vậy, tôi đã thêm mã để cho phép WeakContainer tương đương.

Trong trường hợp bạn muốn sử dụng WeakContainer trong từ điển, tôi cũng đã làm cho nó có thể băm để nó có thể được sử dụng làm khóa từ điển.

Tôi cũng đã đổi tên nó thành WeakObject để nhấn mạnh rằng điều này chỉ dành cho các loại lớp và để phân biệt nó với các ví dụ của 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!
}

Điều này cho phép bạn thực hiện một số nội dung thú vị như sử dụng Từ điển tham chiếu yếu:

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

Dưới đây là cách thực hiện @ câu trả lời tuyệt vời GoZoner của phù hợp với Hashable, vì vậy nó có thể được lập chỉ mục trong các đối tượng container như: Set, Dictionary, Arrayvv

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đã tự động xử lý hầu hết các vấn đề này, tôi đã giải quyết vấn đề bằng cách tạo một trình bao bọc an toàn cho nó, điều này giúp tránh được rất nhiều câu trả lời trong các câu trả lời khác:

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
}

Ví dụ sử dụng:

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

Nó hoạt động nhiều hơn trước, nhưng việc sử dụng trong phần còn lại của mã của bạn sạch hơn IMO. Nếu bạn muốn làm cho nó giống mảng hơn, bạn thậm chí có thể triển khai đăng ký, biến nó thành một SequenceTypev.v. (nhưng dự án của tôi chỉ cần appendforEachvì vậy tôi không có mã chính xác trong tay).


2

Tuy nhiên, một giải pháp khác cho cùng một vấn đề ... trọng tâm của vấn đề này là lưu trữ một tham chiếu yếu đến một đối tượng nhưng cũng cho phép bạn lưu trữ một cấu trúc.

[Tôi không chắc nó hữu ích như thế nào, nhưng phải mất một thời gian để có được cú pháp đúng]

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

Các câu trả lời khác đã bao quát các góc chung. Tôi nghĩ rằng tôi sẽ chia sẻ một số mã đơn giản bao gồm các nilgóc.

Tôi muốn có một mảng tĩnh (thỉnh thoảng đọc) của tất cả các Labels hiện đang tồn tại trong ứng dụng, nhưng không muốn thấy nilnơi mà những cái cũ đã từng ở.

Không có gì lạ mắt, đây là mã của tôi ...

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

Còn việc sử dụng flatMapthay vì filter& map?
Lukas Kubanek

0

Tôi dựa trên điều này dựa trên công việc của @Eonil, vì tôi yêu thích chiến lược liên kết yếu đóng cửa, nhưng tôi không muốn sử dụng một toán tử hàm cho một biến, vì nó cảm thấy cực kỳ trực quan

Những gì tôi đã làm, thay vào đó, như sau:

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

Bằng cách này bạn có thể làm một cái gì đó như:

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

Đây là giải pháp của tôi:

  • Dọn dẹp mảng khi bị hủy , vì WeakObjectSet đang lưu trữ và không thoát khỏi WeakObject
  • Giải quyết lỗi nghiêm trọng khi tìm thấy phần tử trùng lặp trong 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)) }
    }
}

0

Đây là một bộ sưu tập an toàn chứa các vật chứa yếu. Nó cũng tự động loại bỏ nil container / Wrapper khi truy cập.

Thí dụ:

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

Bộ sưu tập tùy chỉnh 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

Điều gì về một phương pháp chức năng ?

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

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

Đây là ý tưởng chính, sau đó thêm bất kỳ logic tiện lợi nào để theo dõi những gì trong mảng. Ví dụ, người ta có thể xem xét cách tiếp cận tương tự với Từ điển bằng cách sử dụng khóa như một cách tìm thấy những gì trong đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.