Reputation: 6306
The problem is this:
I want to create a class using generics. That class has a delegate that contains functions that take values of the generic type as arguments. The delegate is declared using a protocol.
For some reason, I cannot get this to compile in Swift 5. This is the code:
protocol SelectItemViewControllerDelegate: AnyObject {
func selectItemViewController<T>(_ vc: SelectItemViewController<T>, selectedItem: T) where T: CustomStringConvertible
}
class SelectItemViewController<T> where T: CustomStringConvertible {
weak var delegate: SelectItemViewControllerDelegate?
}
class MyClass: SelectItemViewControllerDelegate {
private var selectItemCompletionBlock: ((String?) -> Void)?
func selectItemViewController<String>(_ vc: SelectItemViewController<String>, selectedItem: String) {
selectItemCompletionBlock?(selectedItem)
}
}
The error I get is:
error: cannot convert value of type 'String' to expected argument type 'String?'
selectItemCompletionBlock?(selectedItem)
^
as! String
When I do this:
selectItemCompletionBlock?(selectedItem as? String)
I get
error: cannot convert value of type 'String?' to expected argument type 'Swift.String?'
selectItemCompletionBlock?(selectedItem as? String)
^
as! String
What is going on here?
Upvotes: 1
Views: 81
Reputation: 273380
The String
here:
func selectItemViewController<String>
declares a generic parameter called String
. It does not refer to Swift.String
, which selectItemCompletionBlock
can accept.
Since SelectItemViewController
is generic, you want its delegate to specify for which T
it is implementing the delegate. In the case of MyClass
, it is String
. To do this, you need an associated type, not a generic method:
protocol SelectItemViewControllerDelegate {
associatedtype ItemType: CustomStringConvertible
func selectItemViewController(_ vc: SelectItemViewController<ItemType>, selectedItem: ItemType)
}
Now MyClass
can implement selectItemViewController(_:selectedItem:)
just for ItemType == String
:
class MyClass: SelectItemViewControllerDelegate {
private var selectItemCompletionBlock: ((String?) -> Void)?
func selectItemViewController(_ vc: SelectItemViewController<String>, selectedItem: String) {
selectItemCompletionBlock?(selectedItem)
}
}
The problem now becomes, this is now invalid
weak var delegate: SelectItemViewControllerDelegate?
You can't use a protocol with associated types as the type of a property. To solve this, you need to create a type eraser:
struct AnySelectItemViewControllerDelegate<T: CustomStringConvertible>: SelectItemViewControllerDelegate {
let selectedItemFunc: (SelectItemViewController<T>, T) -> Void
init<DelegateType: SelectItemViewControllerDelegate>(_ del: DelegateType) where DelegateType.ItemType == T {
// keep a weak reference to del here, so as not to cause a retain cycle
selectedItemFunc = { [weak del] x, y in del?.selectItemViewController(x, selectedItem: y) }
}
func selectItemViewController(_ vc: SelectItemViewController<T>, selectedItem: T) {
selectedItemFunc(vc, selectedItem)
}
}
Then, you can declare the delegate as:
var delegate: AnySelectItemViewControllerDelegate<T>?
And to assign the delegate, create an AnySelectItemViewControllerDelegate
. Suppose self
is the delegate...
selectItemVC.delegate = AnySelectItemViewControllerDelegate(self)
That's a lot of work isn't it? If you only have few delegate methods, I suggest not using a delegate. Just declare some closure properties in SelectItemVC
:
var onItemSelected: ((T) -> Void)?
Upvotes: 1