Як я можу оголосити масив слабких посилань у Swift?


179

Я хотів би зберегти масив слабких посилань у Swift. Сам масив не повинен бути слабким орієнтиром - його елементи повинні бути. Я думаю, що какао NSPointerArrayпропонує небезпечну версію цього.


1
Що робити із створенням об'єкта контейнера, який слабо посилається на інший об'єкт, а потім створення масиву з них? (Якщо ви не отримаєте кращої відповіді)
nielsbot

1
чому ви не використовуєте NSPointerArray?
Бастіан

@nielsbot Це старе рішення obj-c :) Щоб зробити його швидким, він повинен бути загальним об'єктом! :) Однак справжньою проблемою є те, як отримати об'єкти, вилучені з масиву, коли посилається об'єкт розміщується.
Султан

2
Правильно, я б хотів щось із параметрами. Я думаю, що я міг би зробити параметризовану обгортку навколо NSPointerArray, але хотів подивитися, чи є альтернативи.
Білл

6
Так само як і інший варіант, існує NSHashTable. Це в основному NSSet, який дозволяє вказати, як він повинен посилатися на об'єкти, які він містить.
Мік Маккаллум

Відповіді:


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ви можете використовувати structабо class.

Також, щоб допомогти зібрати вміст масиву, ви можете зробити щось відповідно до:

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

Використання AnyObjectвище слід замінити на T- але я не думаю, що поточна мова Swift дозволяє розширення, визначене як таке.


11
Як ви видаляєте об’єкти обгортки з масиву, коли їх значення розміщується?
Султан

9
Так, це збій компілятора.
GoZoner

5
Будь ласка, опублікуйте свій код проблеми в новому запитанні; немає підстав для того, щоб дати мені відповідь, коли це може бути ваш код!
GoZoner

2
@EdGamble Наданий код працює як є, але виходить з ладу, якщо ви заміните клас Stuffпротоколом; побачити це пов’язане питання
Тео

2
Структура була б кращою, оскільки вона зберігалася б у стеці, а не потрібна купа.
КПМ

60

Ви можете використовувати NSHashTable зі слабкимObjectsHashTable. NSHashTable<ObjectType>.weakObjectsHashTable()

Для Swift 3: NSHashTable<ObjectType>.weakObjects()

Посилання на клас NSHashTable

Доступний в OS X v10.5 та новіших версій.

Доступно в iOS 6.0 та новіших версіях.


Найкраща відповідь і не витрачайте часу на обгортки!
Раміс

1
Це розумно, але подібно до відповіді GoZoner, це не працює з типами, які є, Anyале не є AnyObject, наприклад, протоколами.
Аарон Брагер

@SteveWilford Але протокол може бути реалізований класом, що зробить його еталонним типом
Аарон Брагер

4
протокол може розширювати клас, і тоді ви можете використовувати його як слабкий (наприклад, протокол MyProtocol: class)
Ясмін Тьомкін

1
Я отримую помилку компілятора з MyProtocol: classі NSHashTable<MyProtocol>.weakObjects(). "" NSHashTable "вимагає, щоб" MyProtocol "був типом класу.
Грег,

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. Тому що тип String не є AnyType. Моя швидка версія є Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

Код можна схопити з Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** ДОБАВЕНО У НОВАХ.2017

Я оновив код до Swift 4

// Swift 4, Xcode Version 9.1 (9B55)

class WeakObject<T: AnyObject>: Equatable, Hashable {
    weak var object: T?
    init(object: T) {
        self.object = object
    }

    var hashValue: Int {
        if var object = object { return UnsafeMutablePointer<T>(&object).hashValue }
        return 0
    }

    static func == (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
        return lhs.object === rhs.object
    }
}

class WeakObjectSet<T: AnyObject> {
    var objects: Set<WeakObject<T>>

    init() {
        self.objects = Set<WeakObject<T>>([])
    }

    init(objects: [T]) {
        self.objects = Set<WeakObject<T>>(objects.map { WeakObject(object: $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

Це не моє рішення. Я знайшов це на Форумах розробників Apple .

@GoZoner має хорошу відповідь, але це збій компілятора Swift.

Ось версія контейнера слабкого об'єкта не дає збій поточному випущеному компілятору.

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

    init (value: T) {
        _value = value
    }

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

Потім ви можете створити масив цих контейнерів:

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

1
дивно, але більше не працює зі структурами. Каже EXC_BAD_ACCESSпро мене. З класу працює просто відмінно
Мент

6
Структури - цінні типи, вони не повинні працювати з ними. Той факт, що він зазнав аварії під час виконання, а не помилка під час компіляції, є помилкою компілятора.
Девід Гудін

10

Це можна зробити, створивши об’єкт обгортки для утримання слабкого вказівника.

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

А потім використовуючи ці в масиві

var weakThings = WeakThing<Foo>[]()

Повинно бути classв використання weakваров
Білл

3
Хто каже? Код, що знаходиться вище, для мене добре працює. Єдина вимога полягає в тому, що об’єкт, який стає слабким, повинен бути класом, а не об'єктом, який має слабке посилання
Джошуа Вайнберг

Вибачте. Я міг присягнути, щойно я отримав повідомлення компілятора, яке сказало: "Не можна використовувати слабкі змінні в структурах". Ви маєте рацію - це компілює.
Білл

5
@JoshuaWeinberg що робити, якщо Foo є протоколом?
onmyway133

@ onmyway133 AFAIK, якщо протокол оголошено реалізованим лише класами, він буде працювати. protocol Protocol : class { ... }
олейняк

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
Питання в тому, як створити масив (або сказати Set) із слабких об’єктів.
Девід Н

За допомогою цього рішення ви навіть можете створити масив з кількома значеннями, як-от 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()твердженнями.


Так просто використовуйте для об'єкта в
hashtable.allObjects

6

Деталі

  • Швидкий 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?]() }
Василь Боднарчук

Ідея полягає в тому, що цей масив може містити об'єкти різних типів, які реалізують протокол одного класу. Використовуючи загальний, ви блокуєте його до одного типу. Наприклад, уявіть собі синглтон, який містить такий масив як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())
Василь Боднарчук

Тепер ви втратили загальну частину з масивом, що трохи важливо :) У мене таке відчуття, що це просто не можливо. Обмеження Свіфта на даний момент ...
Матік Облак

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, так що він може бути проіндексовані в об'єкти Container , як: 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. Якщо ви хочете , щоб зробити його більш масив типу, ви можете навіть реалізувати індексації, зробити це 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кут.

Я хотів статичний масив (читати періодично) усіх Labels, які зараз існують у додатку, але не хотів бачити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?
Лукас Кубанек

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
  • Вирішіть фатальну помилку, коли дублікат елемент знайдений у 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

Це безпечна колекція типу, яка вміщує контейнери зі слабкими предметами. Він також автоматично видаляє нульові контейнери / обгортки, коли це доступ.

Приклад:

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

Це головна ідея, а потім додайте будь-яку логіку зручності, щоб відстежувати, що є в масиві. Наприклад, можна розглянути той самий підхід зі словником, використовуючи ключ як спосіб знайти те, що там є.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.