我遇到了通用委托ProducerDelegate
的问题,它会有一个与消费者Int
方法需要的类型相同的参数(IntConsumer
)(Int
)
如果将调用委托方法,并且我想使用收到的值element
func didProduce<Int>(from: Producer<Int>, element: Int) {
output(element: element)
}
调用另一种方法我收到错误:
Cannot convert value of type 'Int' to expected argument type 'Int'
我的问题是为什么?
我解释了我的情况(这里是一个具有相同来源的游乐场文件:http://tuvalu.s3.amazonaws.com/so/generic-delegate.playground.zip)
我有一个通用生成器类Producer
,其中包含生成元素ProducerDelegate
的协议:
import Foundation
/// Delegate for produced elements
protocol ProducerDelegate : class {
/// Called if a new element is produced
///
/// - Parameters:
/// - from: producer
/// - element: produced element
func didProduce<T>(from: Producer<T>, element: T)
}
/// Produces new element
class Producer<T> {
/// The object that acts as consumer of produced element
weak var delegate: ProducerDelegate?
/// The producing element
let element: T
/// Initializes and returns a `Producer` producing the given element
///
/// - Parameters:
/// - element: An element which will be produced
init(element: T) {
self.element = element
}
/// Produces the object given element
func produce() {
delegate?.didProduce(from: self, element: element)
}
}
在消费者中,生产者被注入:
/// Consumes produced `Int` elements and work with it
class IntConsumer {
/// Producer of the `Int`s
let producer: Producer<Int>
/// Initializes and returns a `IntConsumer` having the given producer
///
/// - Parameters:
/// - producer: `Int` producer
init(producer: Producer<Int>) {
self.producer = producer
self.producer.delegate = self
}
/// outputs the produced element
fileprivate func output(element: Int) {
print(element)
}
}
现在,我想为代理添加扩展名,如下所示:
extension IntConsumer: ProducerDelegate {
func didProduce<Int>(from: Producer<Int>, element: Int) {
output(element: element)
}
}
但是,它失败了:
的 Cannot convert value of type 'Int' to expected argument type 'Int'
Swift编译器说我应该将元素转换为Int
,如:
func didProduce<Int>(from: Producer<Int>, element: Int) {
output(element: element as! Int)
}
但也失败了
但是,如果泛型类型具有其他具体类型,例如String
,我可以强制转换它的工作原理:
func didProduce<String>(from: Producer<String>, element: String) {
guard let element2 = element as? Int else { return }
output(element: element2)
}
所以,我目前的解决方案是使用一个typealias,我不必在委托方法中输入错误的类型:
extension IntConsumer: ProducerDelegate {
typealias T = Int
func didProduce<T>(from: Producer<T>, element: T) {
guard let element = element as? Int else { return }
output(element: element)
}
}
我希望有人能解释我的错误并给我一个更好的解决方案。
答案 0 :(得分:2)
您的协议要求
func didProduce<T>(from: Producer<T>, element: T)
说&#34;我可以用任何类型的元素和相同类型元素的生产者&#34;来调用。但这不是您想要表达的内容 - IntConsumer
只能 消费Int
元素。
然后将此要求实现为:
func didProduce<Int>(from: Producer<Int>, element: Int) {...}
定义了一个名为&#34; Int
&#34;的新通用占位符。 - 这将影响方法内的标准库Int
。因为你的&#34; Int
&#34;可以表示任何类型,编译器正确地告诉您,您无法将其传递给期望实际 Int
的参数。
你不想在这里使用泛型 - 你需要associated type代替:
/// Delegate for produced elements
protocol ProducerDelegate : class {
associatedtype Element
/// Called if a new element is produced
///
/// - Parameters:
/// - from: producer
/// - element: produced element
func didProduce(from: Producer<Element>, element: Element)
}
此协议要求现在说&#34;我只能使用特定类型的元素调用,符合类型将决定&#34;。
然后您可以简单地将要求实现为:
extension IntConsumer : ProducerDelegate {
// Satisfy the ProducerDelegate requirement – Swift will infer that
// the associated type "Element" is of type Int.
func didProduce(from: Producer<Int>, element: Int) {
output(element: element)
}
}
(请注意删除<Int>
通用占位符)。
但是,由于我们现在使用的是关联类型,因此您无法将ProducerDelegate
用作实际类型 - 只能使用通用占位符。这是因为如果仅以ProducerDelegate
进行通信,编译器现在不知道关联类型是什么,因此您不可能使用依赖于该关联类型的协议要求。
此问题的一个可能解决方案是定义type erasure以包装委托方法,并允许我们根据通用占位符表达关联类型:
// A wrapper for a ProducerDelegate that expects an element of a given type.
// Could be implemented as a struct if you remove the 'class' requirement from
// the ProducerDelegate.
// NOTE: The wrapper will hold a weak reference to the base.
class AnyProducerDelegate<Element> : ProducerDelegate {
private let _didProduce : (Producer<Element>, Element) -> Void
init<Delegate : ProducerDelegate>(_ base: Delegate) where Delegate.Element == Element {
_didProduce = { [weak base] in base?.didProduce(from: $0, element: $1) }
}
func didProduce(from: Producer<Element>, element: Element) {
_didProduce(from, element)
}
}
为了防止保留周期,类型擦除会轻微捕获base
。
然后,您想要更改Producer
的{{1}}媒体资源,以使用此类型删除的包装:
delegate
然后在var delegate: AnyProducerDelegate<Element>?
中分配代理时使用包装器:
IntConsumer
虽然这种方法的一个缺点是如果消费者被解除分配,/// Consumes produced `Int` elements and work with it
class IntConsumer {
// ...
init(producer: Producer<Int>) {
self.producer = producer
self.producer.delegate = AnyProducerDelegate(self)
}
// ...
}
不会被设置为delegate
,而是在其上调用nil
将会默默地失败。不幸的是,我不知道有更好的方法来实现这一目标 - 如果其他人有更好的想法,肯定会感兴趣。