Swift 3:具有具体使用者类型的泛型委托类型的类型错误 [英] Swift 3: Type error of generic delegate type with concrete consumer type

查看:45
本文介绍了Swift 3:具有具体使用者类型的泛型委托类型的类型错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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)

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

If the delegate methods will be called and I want to use the received 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'

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

我的问题是为什么?

我解释了我的情况(这是一个具有相同来源的游乐场文件:

I explain my case (and here is a playground file with the same source: http://tuvalu.s3.amazonaws.com/so/generic-delegate.playground.zip )

我有一个通用的生产者类Producer,其中有一个生产者元素ProducerDelegate的协议:

I have a generic producer class Producer with a protocol for produced elements 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)
    }
}

但是,它失败并显示: Cannot convert value of type 'Int' to expected argument type 'Int'

But, it fails with: Cannot convert value of type 'Int' to expected argument type 'Int'

Swift编译器说我应该将元素强制转换为Int,例如:

The Swift compiler says I should cast the element to Int, like:

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

但它也失败

但是,如果泛型类型具有其他具体类型,例如String,我可以进行强制转换并起作用:

But, if the generic type has an other concrete type, like String, I can cast and it works:

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:

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.

推荐答案

您的协议要求

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

说可以用 any 类型的元素和相同类型的元素的生产者来调用我".但这不是您要表达的内容-IntConsumer只能 消耗Int元素.

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.

然后,您可以通过以下方式实现此要求:

You then implement this requirement as:

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

定义了称为"Int"的 new 通用占位符–将在方法内部隐藏标准库的Int.因为您的"Int"可以代表任何类型,所以编译器正确地告诉您,您不能将其传递给需要 actual Int的参数.

which defines a new generic placeholder called "Int" – which will shadow the standard library's Int inside the method. 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.

您在这里不需要泛型–您需要一个

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

(请注意,删除了<Int>通用占位符).

(Note the removal of the <Int> generic placeholder).

但是,由于我们现在使用的是关联类型,因此您不能将ProducerDelegate用作实际类型-只能使用通用占位符.这是因为如果仅按照ProducerDelegate进行交谈,编译器现在不知道关联的类型是什么,因此您可能无法使用依赖于该关联类型的协议要求.

However, because we're now using an associated type, you cannot use ProducerDelegate as an actual type – only a generic placeholder. 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.

解决此问题的一种可能方法是定义类型擦除,以包装委托方法,并允许我们使用通用占位符来表达关联的类型:

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

为了防止保留周期,通过类型擦除将base捕获得很弱.

In order to prevent retain cycles, base is captured weakly by the type-erasure.

然后,您需要更改Producerdelegate属性,以使用此类型擦除的包装器:

You'll then want to change your Producer's delegate property to use this type-erased wrapper:

var delegate: AnyProducerDelegate<Element>?

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

and then use the wrapper when assigning the delegate in 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,而是调用didProduce只会无声地失败.不幸的是,我不知道实现此目标的更好方法-如果其他人有更好的主意,肯定会感兴趣.

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. Unfortunately, I'm not aware of a better way of achieving this – would certainly be interested if anyone else has a better idea.

这篇关于Swift 3:具有具体使用者类型的泛型委托类型的类型错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆