【问题标题】:Trouble with Swift Protocols, associatedtypes, Self and default implementationsSwift 协议、关联类型、自我和默认实现的问题
【发布时间】:2018-10-03 02:35:29
【问题描述】:

我试图通过我无法确定的默认实现来获得一些功能。考虑下面的代码,它是对我正在尝试做的事情的简化,但尽可能简单地捕获了问题。

//protocol definition
protocol Configurable {
    associatedtype Data
    func configure(data: Data)

    static func generateObject() -> Self
}

//default implementation for any UIView
extension Configurable where Self: UIView {
    static func generateObject() -> Self {
        return Self()
    }
}

//implement protocol for UILabels
extension UILabel: Configurable {
    typealias Data = Int

    func configure(data: Int) {
        label.text = "\(data)"
    }
}

//use the protocol
let label = UILabel.generateObject()
label.configure(data: 5)
print(label.text!)  //5

我有一个协议,UIView 的某些方法的默认实现,以及 UILabel 的特定实现。

我的问题是最后一部分......所有这些功能的实际使用

let label = UILabel.generateObject()
label.configure(data: 5)
print(label.text!)  //5

我发现自己一直在做generateObject(),然后是configure(data: <something>)。所以我尝试了以下操作:

static func generateObjectAndConfigure(data: Data) -> Self 添加到协议中。当我尝试为此方法为 UIView 进行默认实现时,问题就出现了。我收到以下错误

Method 'generateObjectAndConfigure(data:)' in non-final class 'UILabel' cannot be implemented in a protocol extension because it returnsSelfand has associated type requirements

基本上,我不能有一个返回Self 并使用关联类型的方法。总是连续调用这两种方法对我来说真的很讨厌。我只想为每个班级声明configure(Data) 并免费获得generateObjectAndConfigure(Data)

有什么建议吗?

【问题讨论】:

  • 请注意,某些类的默认实现会崩溃,例如UICollectionView。同样使用Self() 有点滥用了无参数初始化器仍然可用UIView 的事实,即使对于许多子类来说调用它没有意义。最好将这两者分开:实例化和配置。

标签: ios swift swift-protocols associated-types default-implementation


【解决方案1】:

使用Self 有点过于复杂了。

您需要做的就是在您的Configurable 协议中声明一个初始化器,它接受您的Data associatedtype 作为参数,并具有非静态配置函数:

protocol Configurable {
    associatedtype Data
    init(data: Data)
    func configure(data: Data)
}

Configurable 协议的扩展中提供该初始化程序的默认实现(对于UIView 及其子类):

extension Configurable where Self: UIView {
    init(data: Data) {
        self.init(frame: CGRect.zero)
        self.configure(data: data)
    }
}

最后,通过对您感兴趣的任何UIView 子类的扩展来添加对协议的一致性。您需要做的就是实现typealiasconfigure 方法:

extension UILabel: Configurable {
typealias Data = Int
func configure(data: Data) {
    text = "\(data)"
}

}

extension UIImageView: Configurable {
    typealias Data = String
    func configure(data: Data) {
        image = UIImage(named: data)
    }
}

这个实现还有一个额外的好处是您使用初始化器来创建视图(用于实例化对象的标准 Swift 模式),而不是静态方法:

let label = UILabel(data: 10)
let imageView = UIImageView(data: "screenshot")

我不清楚为什么编译器不喜欢你的版本。我原以为UILabel 的子类会继承typealias,这意味着编译器在推断SelfData 时应该没有问题,但显然这还不支持。

编辑:@Cristik 在 cmets 中对 UICollectionView 提出了很好的观点。

可以通过为Configurable 添加协议扩展来解决此问题,其中SelfUICollectionView,使用适当的初始化程序:

extension Configurable where Self: UICollectionView {
    init(data: Data) {
        self.init(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
        self.configure(data: data)
    }
}

然后,当为UICollectionView 添加对Configurable 的一致性时,我们将Data typealias 设为UICollectionViewLayout

extension UICollectionView: Configurable {
    typealias Data = UICollectionViewLayout
    func configure(data: Data) {
        collectionViewLayout = data
    }
}

就个人而言,我认为对于 init(frame:) 初始值设定项不合适的类来说,这是一种合理的方法。

【讨论】:

  • “你有点过于复杂了”......天哪,是的!您的 init() 方法非常有效!谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-16
  • 1970-01-01
  • 1970-01-01
  • 2018-07-16
  • 1970-01-01
相关资源
最近更新 更多