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

时间:2016-12-25 08:22:10

标签: swift generics

我遇到了通用委托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)
    }
}

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

1 个答案:

答案 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将会默默地失败。不幸的是,我不知道有更好的方法来实现这一目标 - 如果其他人有更好的想法,肯定会感兴趣。