【发布时间】:2017-12-09 02:53:41
【问题描述】:
一些背景
类型擦除容器在 Swift 中是有用的结构,因为它目前无法支持传递泛型类型参数。社区对此有一些很好的解释:
- http://www.russbishop.net/type-erasure
- https://realm.io/news/tryswift-gwendolyn-weston-type-erasure/
- https://www.bignerdranch.com/blog/breaking-down-type-erasures-in-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) {
}
}
这些类型擦除框允许我们构建通用容器或创建必须符合具有特定类型的通用协议但不限于具体实现的属性。例如使用下面的AnyView,我可以轻松地在视图测试替身中交换。
struct TypeUnderTest {
var view: AnyView<ExampleViewModel>
}
var example = Example(viewModel: ExampleViewModel(content: "hello"))
var instanceUnderTest = TypeUnderTest(view: AnyView(example))
到目前为止一切顺利。我可以类似地将View 定义为具有可选或非可选(而不是隐式展开的可选)viewModel 属性并相应地按框更新。
但是,如果我希望我的类型擦除属性成为 weak 引用怎么办?
weak var view: AnyView<ExampleViewModel> 不好。这将只留下对盒子类型的弱引用,并且会立即被释放。
var view: WeakAnyView<ExampleViewModel> 让我们更接近。我们可以创建一个弱引用其内容的盒子。如果我们的View 协议只定义了可选属性,那么我们就可以开始了:
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
但是,如果我删除的协议 (View) 定义了非可选属性,那么我们就有问题了。 _ViewBox 必须定义一个非可选的 viewModel 以符合 View 但这迫使我们忽略我们的弱引用盒装类型将被释放的非常现实的可能性,并且我们没有一种安全的方式来传达这一点给来电者。
一种选择是添加另一个盒子,但这只是使用起来很痛苦:
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()
}
init(viewModel: T) {
self.viewModel = viewModel
}
var empty: Bool {
get {
return false
}
}
}
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) {
super.init(viewModel: base.viewModel)
self.base = base
}
override func render(_ viewModel: Base.ViewModel) {
base?.render(viewModel)
}
override var empty: Bool {
get {
return base == nil
}
}
}
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)
}
var empty: Bool {
return _box.empty
}
}
struct AnyViewOptionalBox<T: Equatable> {
private var _view: AnyView<T>?
var view: AnyView<T>? {
get {
if let view = self._view, view.empty == false {
return view
} else {
return nil
}
}
set {
self._view = newValue
}
}
init(view: AnyView<T>) {
self.view = view
}
}
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 viewBox: AnyViewOptionalBox<ExampleViewModel>
}
let viewModel = ExampleViewModel(content: "hello")
var example: Example? = Example(viewModel: viewModel)
let anyView: AnyView<ExampleViewModel> = AnyView(example!)
let anyViewOptional: AnyViewOptionalBox<ExampleViewModel> = AnyViewOptionalBox(view: anyView)
let instanceUnderTest = TypeUnderTest(viewBox: anyViewOptional)
instanceUnderTest.viewBox.view?.viewModel.content
example = nil
instanceUnderTest.viewBox.view?.viewModel.content
有没有更好的方法来维护对类型擦除属性的弱引用?
【问题讨论】:
-
为长代码示例道歉,但至少您可以将这些全部放到操场上看看发生了什么。
标签: swift