简体   繁体   English

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

[英]Swift 3: Type error of generic delegate type with concrete consumer type

I have a problem with a generic delegate ProducerDelegate , which will have an argument ( Int ) with the same type as the consumer IntConsumer method needs it ( Int ) 我有一个泛型委托ProducerDelegate的问题,它将有一个参数( Int )与消费者IntConsumer方法需要它相同的类型( Int

If the delegate methods will be called and I want to use the received value element 如果将调用委托方法,我想使用收到的value element

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

to call an other method I got the error: 调用另一种方法我得到了错误:

Cannot convert value of type 'Int' to expected argument type 'Int'

And my question is why? 我的问题是为什么?

I explain my case (and here is a playground file with the same source: http://tuvalu.s3.amazonaws.com/so/generic-delegate.playground.zip ) 我解释了我的情况(这里是一个具有相同来源的游乐场文件: http//tuvalu.s3.amazonaws.com/so/generic-delegate.playground.zip

I have a generic producer class Producer with a protocol for produced elements ProducerDelegate : 我有一个泛型生产者类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)
    }
}

In a consumer, the producer is injected: 在消费者中,生产者被注入:

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

Now, I wanted add the extension for the delegate like this: 现在,我想为代理添加扩展名,如下所示:

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

But, it fails with: Cannot convert value of type 'Int' to expected argument type 'Int' 但是,它失败了: Cannot convert value of type 'Int' to expected argument type 'Int'

The Swift compiler says I should cast the element to Int , like: Swift编译器说我应该将元素转换为Int ,如:

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

but it fails, too 但它也失败了

But, if the generic type has an other concrete type, like String , I can cast and it works: 但是,如果泛型类型具有其他具体类型,如String ,我可以强制转换它的工作原理:

func didProduce<String>(from: Producer<String>, element: String) {
    guard let element2 = element as? Int else { return }

    output(element: element2)
}

So, my current solution is to work with a typealias, that I don't have to put wrong types in the delegate method: 所以,我目前的解决方案是使用一个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)
    }
}

I hope someone can explain me my error and give me a better solution. 我希望有人可以解释我的错误并给我一个更好的解决方案。

Your protocol requirement 您的协议要求

func didProduce<T>(from: Producer<T>, element: T)

says "I can be called with any type of element and a producer of the same type of element". 说:“我可以使用任何类型的元素和相同类型元素的生产者来调用”。 But that's not what you want to express – an IntConsumer can only consume Int elements. 但这不是你想要表达的 - 一个IntConsumer 只能消耗Int元素。

You then implement this requirement as: 然后,您将此要求实现为:

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

which defines a new generic placeholder called " Int " – which will shadow the standard library's Int inside the method. 它定义了一个名为“ Int ”的通用占位符 - 它将在方法中Int标准库的Int Because your " Int " could represent any type, the compiler rightly tells you that you cannot pass it to a parameter that expects an actual Int . 因为您的Int ”可以表示任何类型,所以编译器正确地告诉您不能将它传递给期望实际 Int的参数。

You don't want generics here – you want an associated type instead: 你不想在这里使用泛型 - 你想要一个相关的类型

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

This protocol requirement now says "I can be called with only a specific type of element, which the conforming type will decide". 此协议要求现在说“我只能使用特定类型的元素调用,符合类型将决定”。

You can then simply implement the requirement as: 然后,您可以简单地将要求实现为:

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

(Note the removal of the <Int> generic placeholder). (注意删除<Int>通用占位符)。

However, because we're now using an associated type, you cannot use ProducerDelegate as an actual type – only a generic placeholder. 但是,因为我们现在使用的是关联类型,所以不能将ProducerDelegate用作实际类型 - 只能使用通用占位符。 This is because the compiler now has no idea what the associated type is if you talk only in terms of ProducerDelegate , so you cannot possibly use protocol requirements that depend on that associated type. 这是因为如果仅根据ProducerDelegate ,编译器现在不知道关联类型是什么,因此您不可能使用依赖于该关联类型的协议要求。

One possible solution to this problem is to define a type erasure in order to wrap the delegate method, and allowing us to express the associated type in terms of a generic placeholder: 解决此问题的一种可能方法是定义类型擦除以包装委托方法,并允许我们根据通用占位符表示关联类型:

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

In order to prevent retain cycles, base is captured weakly by the type-erasure. 为了防止保留周期, base被类型擦除弱捕获。

You'll then want to change your Producer 's delegate property to use this type-erased wrapper: 然后,您需要更改Producerdelegate属性以使用此类型擦除的包装器:

var delegate: AnyProducerDelegate<Element>?

and then use the wrapper when assigning the delegate in IntConsumer : 然后在IntConsumer分配委托时使用包装器:

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

    // ...        

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

    // ...

}

Although, one downside to this approach is that the delegate won't be set to nil if the consumer is deallocated, instead calling didProduce on it will just silently fail. 虽然,一个缺点这种做法是, delegate将不会被设置到nil ,如果消费者被释放,而不是调用didProduce它会只是默默地失败。 Unfortunately, I'm not aware of a better way of achieving this – would certainly be interested if anyone else has a better idea. 不幸的是,我不知道有更好的方法来实现这一点 - 如果其他人有更好的想法,肯定会感兴趣。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM