[swift] Swift에서 약한 참조 배열을 어떻게 선언합니까?

Swift에 약한 참조 배열을 저장하고 싶습니다. 배열 자체는 약한 참조가 아니어야합니다. Cocoa NSPointerArray는 형식이 안전하지 않은 버전을 제공 한다고 생각 합니다.



답변

다음과 같이 일반 랩퍼를 작성하십시오.

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 언어에서 이와 같이 정의 된 확장을 허용하지 않는다고 생각합니다.


답변

weakObjectsHashTable과 함께 NSHashTable을 사용할 수 있습니다. NSHashTable<ObjectType>.weakObjectsHashTable()

스위프트 3의 경우 : NSHashTable<ObjectType>.weakObjects()

NSHashTable 클래스 참조

OS X v10.5 이상에서 사용 가능합니다.

iOS 6.0 이상에서 사용 가능합니다.


답변

파티에 늦었지만 내 시도해보십시오. 배열이 아닌 세트로 구현했습니다.

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은 문자열 유형이 아닌 NSString을 사용합니다. 문자열 유형은 AnyType이 아니기 때문입니다. 나의 신속한 버전은 Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)입니다.

코드는 Gist에서 얻을 수 있습니다.
https://gist.github.com/codelynx/30d3c42a833321f17d39

** 2017 년 11 월 추가

코드를 Swift 4로 업데이트했습니다.

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

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

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

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

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

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

    init(objects: [T]) {
        self.objects = Set<WeakObject<T>>(objects.map { WeakObject(object: $0) })
    }

    var allObjects: [T] {
        return objects.flatMap { $0.object }
    }

    func contains(_ object: T) -> Bool {
        return self.objects.contains(WeakObject(object: object))
    }

    func addObject(_ object: T) {
        self.objects.formUnion([WeakObject(object: object)])
    }

    func addObjects(_ objects: [T]) {
        self.objects.formUnion(objects.map { WeakObject(object: $0) })
    }
}

gokeji가 언급했듯이 NSString은 사용 코드에 따라 할당 해제되지 않는다는 것을 알았습니다. 나는 머리를 긁었고 다음과 같이 MyString 클래스를 작성했습니다.

// typealias MyString = NSString
class MyString: CustomStringConvertible {
    var string: String
    init(string: String) {
        self.string = string
    }
    deinit {
        print("relasing: \(string)")
    }
    var description: String {
        return self.string
    }
}

그런 다음 교체 NSStringMyString같은. 그런 다음 작동한다고 말하면 이상합니다.

var alice: MyString? = MyString(string: "Alice")
var bob: MyString? = MyString(string: "Bob")
var cathline: MyString? = MyString(string: "Cathline")

var persons = WeakObjectSet<MyString>()

persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObjects([alice!, cathline!])
print(persons.allObjects) // [Alice, Cathline, Bob]

alice = nil
print(persons.allObjects) // [Cathline, Bob]

bob = nil
print(persons.allObjects) // [Cathline]

그런 다음 이상한 페이지 가이 문제와 관련이 있다는 것을 알았습니다.

약한 참조는 할당 해제 된 NSString을 유지합니다 (XC9 + iOS Sim 만 해당)

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

문제가 있다고 RESOLVED하지만 여전히이 문제와 관련이 있는지 궁금합니다. 어쨌든 MyString과 NSString의 동작 차이는이 컨텍스트를 벗어나지 만 누군가이 문제를 파악하면 감사하겠습니다.


답변

이것은 내 해결책이 아닙니다. Apple 개발자 포럼에서 찾았습니다 .

@GoZoner는 좋은 답변을 가지고 있지만 Swift 컴파일러와 충돌합니다.

다음은 약한 객체 컨테이너 버전으로 현재 출시 된 컴파일러와 충돌하지 않습니다.

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

    init (value: T) {
        _value = value
    }

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

그런 다음 이러한 컨테이너의 배열을 만들 수 있습니다.

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


답변

약한 포인터를 보유 할 랩퍼 오브젝트를 작성하여이를 수행 할 수 있습니다.

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

그런 다음 배열에서 이것을 사용하십시오.

var weakThings = WeakThing<Foo>[]()


답변

기능적 스타일 래퍼는 어떻습니까?

class Class1 {}

func captureWeakly<T> (_ target:T) -> (() -> T?) where T: AnyObject {
    return { [weak target] in
        return target
    }
}

let obj1 = Class1()
let obj2 = Class1()
let obj3 = Class1()
let captured1 = captureWeakly(obj1)
let captured2 = captureWeakly(obj2)
let captured3 = captureWeakly(obj3)

반환 된 클로저를 호출하여 대상이 아직 살아 있는지 확인하십시오.

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

이 클로저를 배열에 저장할 수 있습니다.

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

클로저 호출을 매핑하여 약하게 캡처 된 값을 검색 할 수 있습니다.

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

실제로 클로저를 만드는 기능이 필요하지 않습니다. 개체를 직접 캡처하십시오.

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


답변

제네릭으로 약한 컨테이너를 만드는 것과 동일한 아이디어가있었습니다.
결과적으로 NSHashTable다음에 대한 래퍼를 만들었습니다 .

class WeakSet<ObjectType>: SequenceType {

    var count: Int {
        return weakStorage.count
    }

    private let weakStorage = NSHashTable.weakObjectsHashTable()

    func addObject(object: ObjectType) {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        weakStorage.addObject(object as? AnyObject)
    }

    func removeObject(object: ObjectType) {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        weakStorage.removeObject(object as? AnyObject)
    }

    func removeAllObjects() {
        weakStorage.removeAllObjects()
    }

    func containsObject(object: ObjectType) -> Bool {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        return weakStorage.containsObject(object as? AnyObject)
    }

    func generate() -> AnyGenerator<ObjectType> {
        let enumerator = weakStorage.objectEnumerator()
        return anyGenerator {
            return enumerator.nextObject() as! ObjectType?
        }
    }
}

용법:

protocol MyDelegate : AnyObject {
    func doWork()
}

class MyClass: AnyObject, MyDelegate {
    fun doWork() {
        // Do delegated work.
    }
}

var delegates = WeakSet<MyDelegate>()
delegates.addObject(MyClass())

for delegate in delegates {
    delegate.doWork()
}

WeakSet모든 유형으로 초기화 할 수 있기 때문에 가장 좋은 해결책 은 아닙니다.이 유형이 AnyObject프로토콜을 준수하지 않으면 자세한 이유와 함께 앱이 중단됩니다. 그러나 지금은 더 나은 해결책이 없습니다.

원래 솔루션은 다음 WeakSet과 같이 정의 했습니다.

class WeakSet<ObjectType: AnyObject>: SequenceType {}

그러나이 경우 WeakSet프로토콜로 초기화 할 수 없습니다 :

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

현재 위의 코드는 컴파일 할 수 없습니다 (Swift 2.1, Xcode 7.1).
그렇기 때문에 어설 션이 AnyObject있는 fatalError()보호자를 추가하고 추가했습니다 .