0

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

//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

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

有什么建议么?

4

1 回答 1

2

You're overcomplicating this a bit, by using Self.

All you need to do is declare an initialiser in your Configurable protocol that accepts your Data associatedtype as an argument, and has a non-static configure function:

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

Provide a default implementation of that initializer in an extension for the Configurable protocol (for UIView and its subclasses):

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

Finally, add conformance to the protocol via an extension to any UIView subclasses you're interested in. All you need to do here is to implement the typealias and configure method:

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)
    }
}

This implementation has the added bonus that you're using an initializer to create your views (the standard Swift pattern for instantiating an object), rather than a static method:

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

It's not exactly clear to me why the compiler doesn't like your version. I would have thought that subclasses of UILabel would inherit the typealias meaning that the compiler shouldn't have a problem inferring both Self and Data, but apparently this isn't supported yet.

Edit: @Cristik makes a good point about UICollectionView in the comments.

This problem can be solved by adding a protocol extension for Configurable where the Self is UICollectionView, using the appropriate initializer:

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

Then, when adding conformance to Configurable for UICollectionView, we make the Data typealias a UICollectionViewLayout:

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

Personally, I think this is a reasonable approach for classes where the init(frame:) initializer isn't appropriate.

于 2018-10-03T03:37:35.303 回答