Swift 3:使用具体的使用者类型键入泛型委托类型的错误

我有一个泛型委托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将默默地失败.不幸的是,我不知道有更好的方法来实现这一点 – 如果其他人有更好的想法,肯定会感兴趣.

点赞