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?
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)?
Related
I'm new to Swift and I'm trying to rewrite a callback as delegation with typealias and I am lost :(
Here is my code:
protocol NewNoteDelegate: class {
typealias MakeNewNote = ((String, String) -> Void)?
}
class NewNoteViewController: UIViewController {
#IBOutlet weak private var titleField: UITextField?
#IBOutlet weak private var noteField: UITextView!
weak var delegate: NewNoteDelegate?
// public var makeNewNote: ((String, String) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
titleField?.becomeFirstResponder()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(didTapSave))
}
#objc func didTapSave() {
if let text = titleField?.text, !text.isEmpty, !noteField.text.isEmpty {
// makeNewNote?(text, noteField.text)
delegate?.MakeNewNote(text, noteField.text)
}
}
}
The errors are:
Cannot convert value of type 'String' to expected argument type '(String, String) -> Void'
Extra argument in call
The original optional callback definition and call is commented out. I first tried rewriting makeNewNote as a typealias without the protocol but still got the same errors.
I also tried removing ? from MakeNewNote but that yielded a new error:
Type '(String, String) -> Void' has no member 'init'
I tried a lot of googling and it's been hours. Can anyone help me figure out what's wrong or point me in the right direction? Thanks in advance.
You are confused. There's no value in defining a typealias in a protocol. You might as well make the typealias global. It just defines a type. You want your protocol to define the methods and properties that conforming objects support:
protocol NewNoteDelegate: class {
func makeNewNote(_: String, _: String)
}
That just means that any object that conforms to the NewNoteDelegate protocol must implement the makeNewNote(:,:) function.
I'm not sure what having a function return Void? even does, so I stripped that away.
Also note that having a function with two anonymous parameters is considered bad form in Swift. You should really name all the parameters (except possibly the first one). In Swift, the names let you know the purpose of each parameter.
Consider this sample code (Compiled as a Mac command line tool, but it could just as easily be a Playground. I just happen to dislike playgrounds.)
import Foundation
protocol NewNoteDelegate: class {
func makeNewNote(_: String, _: String)
}
//The Foo class has a delegate that conforms to the NewNoteDelegate protocol.
class Foo {
weak var delegate: NewNoteDelegate?
func doSomething(string1: String, string2: String) {
//Invoke our delegate, if we have one.
delegate?.makeNewNote(string1, string2)
}
}
//This class just knows how to be a NewNoteDelegate
class ADelegate: NewNoteDelegate {
func makeNewNote(_ string1: String, _ string2: String){
print("string 1 = '\(string1)', string 2 = '\(string2)'")
return
}
}
//Create a Foo object
let aFoo = Foo()
//Create an ADelegate object
let aDelegate = ADelegate()
//Make the ADelegate the Foo object's delegate
aFoo.delegate = aDelegate
//Tell our foo object to do something.
aFoo.doSomething(string1: "string 1", string2: "string 2")
That code outputs
string 1 = 'string 1', string 2 = 'string 2'
I have a generic struct declared as follows:
struct WeakReference<T: AnyObject> {
weak var value: T?
init(value: T?) {
self.value = value
}
}
And a protocol:
protocol SomeProtocol: class {
}
But I'm not able to declare a variable of type of WeakReference<SomeProtocol>, the compiler complains that
'WeakReference' requires that SomeProtocol be a class type
Interestingly, in Swift, the class is a typealias of AnyObject.
I actually want to hold an array of WeakReference<SomeProtocol> because the array holds strong references.
Class-only generic constraints in Swift is a similar question but doesn't really solve this problem.
How can we pass the SomeProtocol to WeakReference?
EDIT:
The following scenario compiles fine, but we lose the ability to hold weak reference:
struct Reference<T> {
var value: T?
init(value: T?) {
self.value = value
}
}
var array: [Reference<SomeProtocol>] = []
Thats simple. You are passing SomeProtocol which is a protocol. You need to pass there specific class type.
Eample:
class SomeImplementation: SomeProtocol {
}
var weakSome: WeakReference<SomeImplementation> = ...
Or you can bypass it by marking the protocol with #objc annotation, but I am not a fan of this approach.
#objc protocol SomeProtocol: class {
}
var weakSome: WeakReference<SomeProtocol> = ...
Try checking this answer, it might provide you more context on the issue.
What do you think about this approach?
class WeakReference<T> {
weak var value: AnyObject?
init(value: T?) {
self.value = value as? AnyObject
}
}
protocol SomeProtocol: class {
}
class A: SomeProtocol { }
let araayOfSomeProtocolObjects: [SomeProtocol] = (0...5).map {_ in A() }
let arrayOfWeakReferences: [WeakReference<SomeProtocol>] = araayOfSomeProtocolObjects.map { WeakReference(value: $0) }
for item in arrayOfWeakReferences {
print(item.value is A) // true
}
I think this should solve your problem.
struct WeakReference<T> {
private weak var privateRef: AnyObject?
var ref: T? {
get { return privateRef as? T }
set { privateRef = newValue as AnyObject }
}
init(_ ref: T? = nil) {
self.ref = ref
}
}
// usage
protocol MyProto: class { }
extension UIViewController: MyProto { }
let vc = UIViewController()
var weakRef = WeakReference<MyProto>(vc)
print(weakRef.ref)
You obviously can use WeakReference with non class protocol or non bridged value types.
If you try that, you'll get always nil.
P.S. : Try this code on a real Xcode project because in the Playground doesn't work as expected.
I've tried to use generic type with protocol:
class Weak<T: AnyObject> {
weak var value: AnyObject?
init (value: AnyObject) {
self.value = value
}
}
protocol SomeProtocol: AnyObject {
func doSomething()
}
func createWeak(object: SomeProtocol) -> Weak<SomeProtocol> {
return Weak<SomeProtocol>(value: object)
}
class SomeClass: SomeProtocol {
func doSomething() {
print("Some 2")
}
}
let temp = SomeClass()
let weakObject = createWeak(object: temp)
weakObject.value?.doSomething()
And got the compiler error:
error: 'SomeProtocol' is not convertible to 'AnyObject'
return Weak(value: object)
But without AnyObject constraint it works fine
class Weak<T> {
var value: T?
init (value: T) {
self.value = value
}
}
protocol Protocol {
func doSomething()
}
class Class: Protocol {
func doSomething() {
print("This is class")
}
}
func createWeak(object: Protocol) -> Weak<Protocol> {
return Weak(value: object)
}
let temp = Class()
let weakObject = createWeak(object: temp)
weakObject.value?.doSomething()
Why I can't use protocols inherited form AnyObject in generic classes?
Swift protocols are incomplete types, which means that you can't use them in places like generic arguments, as the compiler needs to know the whole type details so it can allocate the proper memory layout.
Your createWeak function can still be used if you make it generic:
func createWeak<T: SomeProtocol>(object: T) -> Weak<T> {
return Weak<T>(value: object)
}
The above code works because the compiler will generate at compile time a function mapped to the concrete type you pass.
Even better, you can make the initializer generic, and convert Weak to a struct (value types are preferred Swift over reference ones):
struct Weak<T: AnyObject> {
weak var value: T?
init(_ value: T) {
self.value = value
}
}
which you can use it instead of the free function:
let weakRef = Weak(temp)
Here's the standard boilerplate weak container in Swift.
struct Weak<T: AnyObject> {
weak var value: T?
init(value: T) {
self.value = value
}
}
It works well unless you want T to be a protocol, e.g.,
protocol ImplementationHiding: class {}
class Implementation: ImplementationHiding {}
let w = Weak(value: Implementation() as ImplementationHiding)
This does not compile, sadly. The only way I've found to get it work is to introduce #objc on the protocol:
#objc protocol ImplementationHiding {}
The only way I've found around this is pretty ugly, since it throws out compile-time safety.
struct Weak<T> {
private let get: () -> T?
init(value: AnyObject, type: T.Type = T.self) {
get = { [weak value] in value as! T? }
}
var value: T? {
return get()
}
}
How can I create Weak with a native Swift protocol as T?
If you are fine with losing some compile time safety, you can change your Weak to accept Any as T and then store value as AnyObject:
struct Weak<T: Any>{
weak var value: AnyObject?
public var ref: T? {
get {
return value as? T
}
}
init(value: T) {
self.value = value as AnyObject
}
}
I have a class that needs to call out to a delegate when one of its properties changes. Here are the simplified class and protocol for the delegate:
protocol MyClassDelegate: class {
func valueChanged(myClass: MyClass)
}
class MyClass {
weak var delegate: MyClassDelegate?
var currentValue: Int {
didSet {
if let actualDelegate = delegate {
actualDelegate.valueChanged(self)
}
}
}
init(initialValue: Int) {
currentValue = initialValue
}
}
This all works just fine. But, I want to make this class generic. So, I tried this:
protocol MyClassDelegate: class {
func valueChanged(genericClass: MyClass)
}
class MyClass<T> {
weak var delegate: MyClassDelegate?
var currentValue: T {
didSet {
if let actualDelegate = delegate {
actualDelegate.valueChanged(self)
}
}
}
init(initialValue: T) {
currentValue = initialValue
}
}
This throws two compiler errors. First, the line declaring valueChanged in the protocol gives: Reference to generic type 'MyClass' requires arguments in <...>. Second, the call to valueChanged in the didSet watcher throws: 'MyClassDelegate' does not have a member named 'valueChanged'.
I thought using a typealias would solve the problem:
protocol MyClassDelegate: class {
typealias MyClassValueType
func valueChanged(genericClass: MyClass<MyClassValueType>)
}
class MyClass<T> {
weak var delegate: MyClassDelegate?
var currentValue: T {
didSet {
if let actualDelegate = delegate {
actualDelegate.valueChanged(self)
}
}
}
init(initialValue: T) {
currentValue = initialValue
}
}
I seem to be on the right path, but I still have two compiler errors. The second error from above remains, as well as a new one on the line declaring the delegate property of MyClass: Protocol 'MyClassDelegate' can only be used as a generic constraint because it has Self or associated type requirements.
Is there any way to accomplish this?
It is hard to know what the best solution is to your problem without having more information, but one possible solution is to change your protocol declaration to this:
protocol MyClassDelegate: class {
func valueChanged<T>(genericClass: MyClass<T>)
}
That removes the need for a typealias in the protocol and should resolve the error messages that you've been getting.
Part of the reason why I'm not sure if this is the best solution for you is because I don't know how or where the valueChanged function is called, and so I don't know if it is practical to add a generic parameter to that function. If this solution doesn't work, post a comment.
You can use templates methods with type erasure...
protocol HeavyDelegate : class {
func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R
}
class Heavy<P, R> {
typealias Param = P
typealias Return = R
weak var delegate : HeavyDelegate?
func inject(p : P) -> R? {
if delegate != nil {
return delegate?.heavy(self, shouldReturn: p)
}
return nil
}
func callMe(r : Return) {
}
}
class Delegate : HeavyDelegate {
typealias H = Heavy<(Int, String), String>
func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R {
let h = heavy as! H // Compile gives warning but still works!
h.callMe("Hello")
print("Invoked")
return "Hello" as! R
}
}
let heavy = Heavy<(Int, String), String>()
let delegate = Delegate()
heavy.delegate = delegate
heavy.inject((5, "alive"))
Protocols can have type requirements but cannot be generic; and protocols with type requirements can be used as generic constraints, but they cannot be used to type values. Because of this, you won't be able to reference your protocol type from your generic class if you go this path.
If your delegation protocol is very simple (like one or two methods), you can accept closures instead of a protocol object:
class MyClass<T> {
var valueChanged: (MyClass<T>) -> Void
}
class Delegate {
func valueChanged(obj: MyClass<Int>) {
print("object changed")
}
}
let d = Delegate()
let x = MyClass<Int>()
x.valueChanged = d.valueChanged
You can extend the concept to a struct holding a bunch of closures:
class MyClass<T> {
var delegate: PseudoProtocol<T>
}
struct PseudoProtocol<T> {
var valueWillChange: (MyClass<T>) -> Bool
var valueDidChange: (MyClass<T>) -> Void
}
Be extra careful with memory management, though, because blocks have a strong reference to the object that they refer to. In contrast, delegates are typically weak references to avoid cycles.