Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding a superclass delegate in Swift

I'm working on a Swift (v1.2) project which contains two UIViews. MyView, and MyViewSubclass .

MyView has a delegate that I want to override in MyViewSubclass to be a sub-protocol, similar to how UITableViews have a UITableViewDelegate which also conforms to the super UIScrollViewDelegate.

My first idea was to override the superclass property, but this causes a compiler error, as a subclass can't override a superclass property with a different type.

// Example throws a compiler error. Can't override property with different type

class MyView : UIView {
    weak var delegate : MyViewDelegate?
}

class MyViewSubclass : MyView {
    override weak var delegate : MyViewSubclassDelegate? // Compiler error
}

protocol MyViewDelegate {
   func someFunc ()
}

protocol MyViewSubclassDelegate : MyViewDelegate {
    //func someFunc () Edit: redefinition not necessary, thanks @Rob!
    func someOtherFunc ()
}

My second idea was to override the implicit getter and setter methods of the delegate property. The subclass could have a getter which takes a MyViewSubclassDelegate, and casts down to the MyViewDelegate protocol, so the property itself won't need to be overridden.

This method also results in a compiler error. The accessors can't actually "override" the superclass methods as they have different method signatures, and can't just be declared because their names conflict with the superclass setters.

// Example throws a compiler error. Can't declare function with a conflicting name

class MyViewSubclass : MyView {
    func setDelegate ( newValue : MyViewSubclassDelegate ) { // Compiler error
        super.delegate = newValue as? MyViewDelegate
    }

    func getDelegate () -> MyViewSubclassDelegate { // Compiler error
        return super.delegate as? MyViewSubclassDelegate
    }
}

I could have the subclass override the getter and setter for the superclass, and just store the delegate in a separate subclass property, which does work, but then the subclass will look like it should be assigned a MyViewDelegate, when it really needs to be assigned a MyViewSubclassDelegate, which could cause serious confusion in the future.

// Example works, but it is unclear that MyViewSubclass's delegate should be 
// assigned a MyViewSubclassDelegate

class MyViewSubclass : MyView {
    private weak var _subclassDelegate : MyViewSubclassDelegate?

    override weak var delegate : MyViewDelegate? { // Ambiguous type
        didSet {
            _subclassDelegate = delegate as? MyViewSubclassDelegate
        }
    }
}

I know something at least similar is possible, because classes like UITableView, and UICollectionView seem to do what I want, but they are also written in Objective-C rather than Swift, so the specifics of what the language allows may be different.

Is there any way to have a Swift subclass override a superclass's property with a sub-type?

like image 548
Andrew Gotow Avatar asked Aug 05 '15 17:08

Andrew Gotow


1 Answers

It's not ideal, but if one protocol is inherited from the other, rather than using different types, use the same type, but implement validation in didSet:

class MyView : UIView {

    /// The `MyViewDelegate` delegate

    weak var delegate: MyViewDelegate?
}

class MyViewSubclass: MyView {

    /// The `MyViewSubclassDelegate` delegate.
    ///
    /// **Note: This must be MyViewSubclassDelegate**

    override weak var delegate: MyViewDelegate? {
        didSet {
            assert(delegate == nil || delegate is MyViewSubclassDelegate, "The delegate of MyViewSubclass must be of type `MyViewSubclassDelegate`")
        }
    }
}

It's inelegant, but at least you'll get an immediate runtime error that will bring the problem to the programmer's attention. And by including the /// comment, it will also be shown in the Quick Help, too.

--

Alternatively you can adopt more radical changes, e.g. something like delegate and peoplePickerDelegate properties of ABPeoplePickerNavigationController, where the subclass delegate is specified via a different property.

like image 117
Rob Avatar answered Sep 20 '22 19:09

Rob