Swift 协议、关联类型、Self 和默认实现方面的问题

Trouble with Swift Protocols, associatedtypes, Self and default implementations

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

//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 returns自己and has associated type requirements

基本上,我不能有一个 returns Self 并使用关联类型的方法。老是连续调用这两个方法,感觉真的很讨厌。我只想为每个 class 声明 configure(Data) 并免费获得 generateObjectAndConfigure(Data)

有什么建议吗?

您使用 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 sub类 的扩展来添加对协议的一致性。您在这里需要做的就是实现 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 的 sub类 会继承 typealias,这意味着编译器在推断 SelfData 时应该没有问题,但是显然这还不受支持。

编辑:@Cristik 在评论中提出了关于 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:) 初始值设定项不合适。