【问题标题】:Swift extension storage for protocols用于协议的 Swift 扩展存储
【发布时间】:2016-01-21 20:44:12
【问题描述】:

我制定了一个协议:

protocol SomeProtocol {
    func getData() -> String
}

我做了一个符合它的结构:

struct SomeStruct: SomeProtocol {
    func getData() -> String {
        return "Hello"
    }
}

现在我希望每个UIViewController 都有一个名为source 的属性,所以我可以做类似的事情……

class MyViewController : UIViewController {
    override func viewDidLoad() {
        self.title = source.getData()
    }
}

为此,我创建了一个协议来定义属性:

protocol SomeProtocolInjectable {
    var source: SomeProtocol! { get set }
}

现在我只需要使用这个属性来扩展视图控制器:

extension UIViewController: SomeProtocolInjectable {
    // ???
}

我怎样才能将可与协议类型一起使用的存储属性组合在一起?

什么没用:

还有其他建议吗?

【问题讨论】:

  • 使用静态属性对你有用吗?
  • 这是一个很好的解决方法,但理想情况下不同的视图控制器会有不同的sources。如果你想留下它作为答案,如果没有更好的结果,我会在一两天内接受它。
  • 在下面查看我的答案 - 您可以使用某些 proxy 类型为每个实例实现不同的source...
  • 你一定需要扩展UIViewControllerMyViewController就足够了吗?

标签: swift


【解决方案1】:

任何协议对象都可以转换为类型擦除的类。构建一个 AnySomeProtocol 并存储它。

private var sourceKey: UInt8 = 0

final class AnySomeProtocol: SomeProtocol {
    func getData() -> String { return _getData() }
    init(_ someProtocol: SomeProtocol) { _getData = someProtocol.getData }
    private let _getData: () -> String
}

extension UIViewController: SomeProtocolInjectable {
    var source: SomeProtocol! {
        get {
            return objc_getAssociatedObject(self, &sourceKey) as? SomeProtocol
        }
        set(newValue) {
            objc_setAssociatedObject(self, &sourceKey, AnySomeProtocol(newValue), .OBJC_ASSOCIATION_RETAIN)
        }
    }
}

class MyViewController : UIViewController {
    override func viewDidLoad() {
        self.title = source.getData()
    }
}

调用者只能使用它来访问协议方法。您无法使用 as 将其强制恢复为原始类型,但无论如何您都应该避免这种情况。

作为旁注,我真的建议让source 返回SomeProtocol? 而不是SomeProtocol!。这里没有任何东西可以保证source 将被设置。在viewDidLoad 之前,您甚至都不会设置它。

【讨论】:

  • 如果我理解正确,这将复制任何实现 SomeProtocol 的状态。但除此之外,这不会有效地将SomeStruct 替换为AnySomeProtocol 吗? SomeStruct 会有不同的实现。例如,一种类型可能从 Web 服务获取数据,而另一种类型可能从 Core Data 获取。
  • 这不会复制状态。它转发到结构的实现。 _getData 不是字符串。这是一个返回字符串的函数。即使该结构不可访问,闭包仍会捕获该结构(或捕获您传递的实现此协议的任何其他内容)。这与标准库中的 AnySequence 或 AnyGenerator 非常相似。见robnapier.net/erasure
  • 谢谢!精彩的博文也是如此。
  • 顺便说一句,我故意使用SomeProtocol!:如果我忘记在某处设置source,我希望应用程序尽快崩溃。但总的来说,我同意你关于避免隐式展开的选项。
  • @BrodyRobertson No.
【解决方案2】:

您可以使用静态和视图控制器hash

struct SomeProtocol {/*....*/}

struct DataProxy {
    var data: [Int: SomeProtocol]
}



protocol SomeProtocolInjectable {
    var source: SomeProtocol! { get set }
}

extension UIViewController: SomeProtocolInjectable {

    static var dataProxy = DataProxy(data: [:])

    var source: SomeProtocol! {
        get{
            return UIViewController.dataProxy.data[self.hashValue]
        }
        set{
            UIViewController.dataProxy.data[self.hashValue] = newValue
        }
    }

}

【讨论】:

  • 请注意,这依赖于未记录的行为。它恰好可以工作,因为 UIViewController 通过返回其地址指针来实现哈希,但这不是承诺的。允许两个视图控制器具有相同的哈希值(它们可能不会)。相反,您可以在指向对象的指针上建立索引:Unmanaged.passUnretained(self).toOpaque()(这是一个有效的字典键,但该指针使用起来完全不安全)。请注意,这种方法永远不会释放存储的数据,即使在视图控制器被销毁后也是如此。
  • 聪明!但我同意@RobNapier,我想避免指望无证散列行为。为创造力 +1。
【解决方案3】:

如何为getData() 添加一个默认实现,为协议提供一个虚拟结构实现,并将其用作source 变量的默认值:

protocol SomeProtocol {
    func getData() -> String
}

extension SomeProtocol {
    func getData() -> String {
        return "Hello"
    }
}

protocol SomeProtocolInjectable {
    var source: SomeProtocol { get set }
}

struct DummyProtocolImplementation: SomeProtocol {

}

class MyViewController : UIViewController {
    var _source: SomeProtocol = DummyProtocolImplementation()

    override func viewDidLoad() {
        self.title = source.getData()
    }
}

extension MyViewController: SomeProtocolInjectable {
    var source: SomeProtocol { get { return _source } set { _source = newValue } }
}

我冒昧地扩展了MyViewController 而不是UIViewController,因为后者根本不知道协议。

【讨论】:

  • 这需要在每个视图控制器子类上声明一个属性,这正是我想要避免的。
  • 如果你从MyViewController继承你所有的控制器就不会了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-03
  • 2015-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多