swift 3 : Type error of generic delegate type with concrete consumer type

标签 swift generics

我有一个通用委托(delegate) ProducerDelegate 的问题,它将有一个与消费者 IntConsumer 相同类型的参数 (Int)方法需要它 (Int)

如果将调用委托(delegate)方法并且我想使用接收到的值 element

func didProduce<Int>(from: Producer<Int>, element: Int) {
    output(element: element)
}

调用其他方法时出现错误:

无法将“Int”类型的值转换为预期的参数类型“Int”

我的问题是为什么?

我解释一下我的情况(这里是一个具有相同来源的 playground 文件:http://tuvalu.s3.amazonaws.com/so/generic-delegate.playground.zip)

我有一个通用的生产者类Producer,它带有生产元素ProducerDelegate的协议(protocol):

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

在消费者中,生产者被注入(inject):

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

现在,我想像这样为委托(delegate)添加扩展:

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

因此,我目前的解决方案是使用类型别名,这样我就不必在委托(delegate)方法中输入错误的类型:

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

我希望有人能解释我的错误并给我一个更好的解决方案。

最佳答案

您的协议(protocol)要求

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

这个协议(protocol)要求现在说“我只能用特定类型的元素调用,这将由符合类型决定”。

然后您可以简单地将需求实现为:

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 进行讨论,编译器现在不知道关联的类型是什么。 ,因此您不可能使用依赖于该关联类型的协议(protocol)要求。

这个问题的一个可能的解决方案是定义一个 type erasure为了包装委托(delegate)方法,并允许我们根据通用占位符来表达关联类型:

// 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被类型删除弱捕获。

然后你会想要改变你的 Producerdelegate使用此类型删除包装器的属性:

var delegate: AnyProducerDelegate<Element>?

然后在 IntConsumer 中分配委托(delegate)时使用包装器:

/// Consumes produced `Int` elements and work with it
class IntConsumer {

    // ...        

    init(producer: Producer<Int>) {
        self.producer = producer
        self.producer.delegate = AnyProducerDelegate(self)
    }

    // ...

}

不过,这种方法的一个缺点是 delegate不会设置为 nil如果消费者被释放,而不是调用 didProduce它只会默默地失败。不幸的是,我不知道实现此目标的更好方法 - 如果其他人有更好的想法,我肯定会感兴趣。

关于 swift 3 : Type error of generic delegate type with concrete consumer type,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41319698/

相关文章:

java - 使用通过反射获得的枚举常量

java - "Unchecked generic array creation for varargs parameter of type Matcher <? extends String> []"警告使用 CoreMatchers.allOf()

typescript 参数相互依赖

ios - 需要使用展开/折叠功能按字母顺序对 UITableView 进行分组

ios - 根据选择将 UITableView 滚动到某个 indexPath

ios - 初始化的枚举返回错误的 hashValue

swift - 类型 'ViewController' 不符合协议(protocol) 'UITableViewDataSource' 我不知道为什么

swift - 是否可以向 Swift 协议(protocol)一致性扩展添加类型约束?

java - 如何调用不带参数的泛型方法?

swift - 使用 Swift 发送短信,记住电话号码