我有一个泛型委托ProducerDelegate的问题,它将有一个参数(Int)与消费者IntConsumer方法需要它相同的类型(Int)
如果将调用委托方法,我想使用收到的value元素
func didProduce<Int>(from: Producer<Int>, element: Int) {
output(element: element)
}
调用另一种方法我得到了错误:
无法将’Int’类型的值转换为预期的参数类型’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)
}
}
但是,它失败了:
无法将’Int’类型的值转换为预期的参数类型’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)
}
}
我希望有人可以解释我的错误并给我一个更好的解决方案.
最佳答案 您的协议要求
func didProduce<T>(from: Producer<T>, element: T)
说:“我可以使用任何类型的元素和相同类型元素的生产者来调用”.但这不是你想要表达的 – 一个IntConsumer只能消耗Int元素.
然后,您将此要求实现为:
func didProduce<Int>(from: Producer<Int>, element: Int) {...}
它定义了一个名为“Int”的新通用占位符 – 它将在方法中隐藏标准库的Int.因为您的“Int”可以表示任何类型,所以编译器正确地告诉您不能将它传递给期望实际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)
}
此协议要求现在说“我只能使用特定类型的元素调用,符合类型将决定”.
然后,您可以简单地将要求实现为:
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)
}
}
为了防止保留周期,基类被类型擦除弱捕获.
然后,您需要更改Producer的委托属性以使用此类型擦除的包装器:
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)
}
// ...
}
虽然这种方法的一个缺点是,如果消费者被释放,代理将不会被设置为nil,而是在其上调用didProduce将默默地失败.不幸的是,我不知道有更好的方法来实现这一点 – 如果其他人有更好的想法,肯定会感兴趣.