我们能否在Swift中创建具有非可选属性的类型擦除弱引用? 一些背景

我们能否在Swift中创建具有非可选属性的类型擦除弱引用? 一些背景,swift,Swift,鉴于Swift目前无法支持传递泛型类型参数,类型擦除容器在Swift中是有用的结构。社区对此有一些很好的解释: 下面是一个例子: protocol View: class { associatedtype ViewModel: Equatable var viewModel: ViewModel! { get set } func render(_ viewModel: ViewModel) } class _AnyViewBoxBase<T: Eq

鉴于Swift目前无法支持传递泛型类型参数,类型擦除容器在Swift中是有用的结构。社区对此有一些很好的解释:

下面是一个例子:

protocol View: class {
    associatedtype ViewModel: Equatable

    var viewModel: ViewModel! { get set }

    func render(_ viewModel: ViewModel)
}

class _AnyViewBoxBase<T: Equatable>: View {

    var viewModel: T!

    func render(_ viewModel: T) {
        fatalError()
    }
}

final class _ViewBox<Base: View>: _AnyViewBoxBase<Base.ViewModel> {

    var base: Base!

    override var viewModel: Base.ViewModel! {
        get {
            return base.viewModel
        }
        set {
            base.viewModel = newValue
        }
    }

    init(_ base: Base) {
        self.base = base
    }

    override func render(_ viewModel: Base.ViewModel) {
        base.render(viewModel)
    }
}

final class AnyView<T: Equatable>: View {

    var _box: _AnyViewBoxBase<T>

    var viewModel: T! {
        get {
            return _box.viewModel
        }
        set {
            _box.viewModel = newValue
        }
    }

    func render(_ viewModel: T) {
        _box.render(viewModel)
    }

    init<Base: View>(_ base: Base) where Base.ViewModel == T {
        _box = _ViewBox(base)
    }
}

struct ExampleViewModel {
    let content: String
}

extension ExampleViewModel: Equatable {
    static func ==(lhs: ExampleViewModel, rhs: ExampleViewModel) -> Bool {
        return lhs.content == rhs.content
    }
}

final class Example: View {
    var viewModel: ExampleViewModel!

    init(viewModel: ExampleViewModel) {
        self.viewModel = viewModel
    }

    func render(_ viewModel: ExampleViewModel) {
    }
}

到目前为止还不错。类似地,我可以定义
View
具有可选或非可选(而不是隐式展开的可选)
viewModel
属性,并相应地按框更新

但是,如果我希望我的类型擦除属性是弱引用,该怎么办?
弱var视图:AnyView
不好。这将使我只剩下一个弱引用,它将立即被释放

var视图:WeakAnyView
让我们更接近。我们可以创建一个弱引用其内容的框。如果我们的
视图
协议只定义了可选属性,那么我们可以继续:

protocol View: class {
    associatedtype ViewModel: Equatable

    var viewModel: ViewModel? { get set }

    func render(_ viewModel: ViewModel)
}

class _AnyViewBoxBase<T: Equatable>: View {

    var viewModel: T?

    func render(_ viewModel: T) {
        fatalError()
    }
}

final class _ViewBox<Base: View>: _AnyViewBoxBase<Base.ViewModel> {

    weak var base: Base?

    override var viewModel: Base.ViewModel? {
        get {
            return base?.viewModel
        }
        set {
            base?.viewModel = newValue
        }
    }

    init(_ base: Base) {
        self.base = base
    }

    override func render(_ viewModel: Base.ViewModel) {
        base?.render(viewModel)
    }
}

final class AnyView<T: Equatable>: View {

    var _box: _AnyViewBoxBase<T>

    var viewModel: T? {
        get {
            return _box.viewModel
        }
        set {
            _box.viewModel = newValue
        }
    }

    func render(_ viewModel: T) {
        _box.render(viewModel)
    }

    init<Base: View>(_ base: Base) where Base.ViewModel == T {
        _box = _ViewBox(base)
    }
}

struct ExampleViewModel {
    let content: String
}

extension ExampleViewModel: Equatable {
    static func ==(lhs: ExampleViewModel, rhs: ExampleViewModel) -> Bool {
        return lhs.content == rhs.content
    }
}

final class Example: View {
    var viewModel: ExampleViewModel?

    init(viewModel: ExampleViewModel?) {
        self.viewModel = viewModel
    }

    func render(_ viewModel: ExampleViewModel) {
    }
}

struct TypeUnderTest {
    var view: AnyView<ExampleViewModel>
}

let viewModel = ExampleViewModel(content: "hello")
var example: Example? = Example(viewModel: viewModel)
let instanceUnderTest = TypeUnderTest(view: AnyView(example!))
instanceUnderTest.view.viewModel
example = nil
instanceUnderTest.view.viewModel


有没有更好的方法来维护对类型擦除属性的弱引用?

基本上,您想要的是将类型擦除框的生命周期链接到它包含的对象的生命周期,这样一旦包含的对象被释放,框就会被释放

一种方法是确保长方体仅弱引用包含的对象,并使用objc_setAssociatedObject(…)使长方体成为包含对象的关联对象。这样,基本上可以反转两个对象之间的所有权关系

请参见下面的游乐场示例:

import ObjectiveC

protocol View: class {
    associatedtype ViewModel: Equatable

    var viewModel: ViewModel { get set }

    func render()
}

private var AssociatedObjectHandle: UInt8 = 0

final class AnyView<T: Equatable>: View {

    let _viewModelGetter: () -> T
    let _viewModelSetter: (T) -> Void
    let _render: () -> Void

    init<Base: View>(_ base: Base) where Base.ViewModel == T {
        //Ensure this object doesn't reference base, so there is no retain cycle
        _viewModelGetter = { [weak base] in
            //You can force unwrap, because it is guaranteed that base is not deallocated because of the association
            return base!.viewModel
        }
        _viewModelSetter = { [weak base] in
            //You can force unwrap, because it is guaranteed that base is not deallocated because of the association
            base!.viewModel = $0
        }
        _render = { [weak base] in
            //You can force unwrap, because it is guaranteed that base is not deallocated because of the association
            base!.render()
        }

        //Associate this object with the base, so it gets deallocated when base gets deallocated, also base is guaranteed to exist during our lifetime
        objc_setAssociatedObject(base, &AssociatedObjectHandle, self, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }

    deinit {
        print("dealloc: \(self)")
    }

    var viewModel: T {
        get {
            return _viewModelGetter()
        }
        set {
            _viewModelSetter(newValue)
        }
    }

    func render() {
        _render()
    }
}

class ConcreteView: View {
    typealias ViewModel = String

    var viewModel: String

    init(viewModel: String) {
        self.viewModel = viewModel
    }

    deinit {
        print("dealloc: \(self)")
    }

    func render() {
        print("viewModel: \(viewModel)")
    }
}


weak var anyView: AnyView<String>?
autoreleasepool {
    var concreteView = ConcreteView(viewModel: "Test")
    autoreleasepool {
        anyView = AnyView(concreteView)

        //Any view should render correctly because concrete view exists
        anyView!.render()
    }
    //Success: anyView is not nil yet, because concreteView still exists
    anyView!.render()
}
//Crash: anyView is now nil
anyView!.render()
导入ObjectiveC
协议视图:类{
associatedtype视图模型:可均衡
var viewModel:viewModel{get set}
func render()
}
私有变量AssociatedObjectHandle:UInt8=0
最后一个类AnyView:View{
让_viewModelGetter:()->T
让_viewModelSetter:(T)->Void
让_render:()->Void
init(u-base:base),其中base.ViewModel==T{
//确保此对象不引用基准,因此不存在保留周期
_viewModelGetter={[weake base]在中
//您可以强制展开,因为可以保证不会因为关联而取消分配base
返回基本视图模型
}
_viewModelSetter={[weake base]in
//您可以强制展开,因为可以保证不会因为关联而取消分配base
基本!.viewModel=$0
}
_render={[弱基]在中
//您可以强制展开,因为可以保证不会因为关联而取消分配base
base!.render()
}
//将这个对象和基关联起来,这样当基被解除分配时,它就会被解除分配,并且保证在我们的生命周期中存在基
objc_setAssociatedObject(基本和AssociatedObjectHandle,self,objc_AssociationPolicy.objc_ASSOCIATION_RETAIN_非原子)
}
脱硝{
打印(“解除锁定:\(自我)”)
}
var-viewModel:T{
得到{
返回_viewModelGetter()
}
设置{
_viewModelSetter(新值)
}
}
func render(){
_render()
}
}
类视图:视图{
typealias ViewModel=字符串
var-viewModel:String
初始化(视图模型:字符串){
self.viewModel=viewModel
}
脱硝{
打印(“解除锁定:\(自我)”)
}
func render(){
打印(“视图模型:\(视图模型)”)
}
}
弱var anyView:anyView?
自动释放池{
var concreteView=concreteView(视图模型:“测试”)
自动释放池{
anyView=anyView(混凝土视图)
//任何视图都应正确渲染,因为存在具体视图
anyView!.render()
}
//成功:anyView还不是零,因为concreteView仍然存在
anyView!.render()
}
//崩溃:anyView现在为零
anyView!。render()
输出:

viewModel: Test
viewModel: Test
dealloc: __lldb_expr_34.ConcreteView
dealloc: __lldb_expr_34.AnyView<Swift.String>
Fatal error: Unexpectedly found nil while unwrapping an Optional value
viewModel:测试
视图模型:测试
dealloc:\uuuuLLDB\uExpr\u34.ConcreteView
解除锁定:\ lldb\ expr\ u 34.AnyView
致命错误:在展开可选值时意外发现nil

对于冗长的代码示例表示歉意,但至少您可以将这些示例放在操场上看看发生了什么。
import ObjectiveC

protocol View: class {
    associatedtype ViewModel: Equatable

    var viewModel: ViewModel { get set }

    func render()
}

private var AssociatedObjectHandle: UInt8 = 0

final class AnyView<T: Equatable>: View {

    let _viewModelGetter: () -> T
    let _viewModelSetter: (T) -> Void
    let _render: () -> Void

    init<Base: View>(_ base: Base) where Base.ViewModel == T {
        //Ensure this object doesn't reference base, so there is no retain cycle
        _viewModelGetter = { [weak base] in
            //You can force unwrap, because it is guaranteed that base is not deallocated because of the association
            return base!.viewModel
        }
        _viewModelSetter = { [weak base] in
            //You can force unwrap, because it is guaranteed that base is not deallocated because of the association
            base!.viewModel = $0
        }
        _render = { [weak base] in
            //You can force unwrap, because it is guaranteed that base is not deallocated because of the association
            base!.render()
        }

        //Associate this object with the base, so it gets deallocated when base gets deallocated, also base is guaranteed to exist during our lifetime
        objc_setAssociatedObject(base, &AssociatedObjectHandle, self, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }

    deinit {
        print("dealloc: \(self)")
    }

    var viewModel: T {
        get {
            return _viewModelGetter()
        }
        set {
            _viewModelSetter(newValue)
        }
    }

    func render() {
        _render()
    }
}

class ConcreteView: View {
    typealias ViewModel = String

    var viewModel: String

    init(viewModel: String) {
        self.viewModel = viewModel
    }

    deinit {
        print("dealloc: \(self)")
    }

    func render() {
        print("viewModel: \(viewModel)")
    }
}


weak var anyView: AnyView<String>?
autoreleasepool {
    var concreteView = ConcreteView(viewModel: "Test")
    autoreleasepool {
        anyView = AnyView(concreteView)

        //Any view should render correctly because concrete view exists
        anyView!.render()
    }
    //Success: anyView is not nil yet, because concreteView still exists
    anyView!.render()
}
//Crash: anyView is now nil
anyView!.render()
viewModel: Test
viewModel: Test
dealloc: __lldb_expr_34.ConcreteView
dealloc: __lldb_expr_34.AnyView<Swift.String>
Fatal error: Unexpectedly found nil while unwrapping an Optional value