8

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?

Andrew Gotow
  • 178
  • 2
  • 6
  • Thanks for the suggestion! Inheriting protocols will work (and I'm doing that in the example), but it still ends up with the same problem as example 3, where the delegate for the subclass appears as though it should be the super protocol, where the sub-protocol delegate is required. For now, I think I'll stick with having two separate parameters as per your first comment until I can find a solution a bit more like that employed by UITableViewDelegate. – Andrew Gotow Aug 05 '15 at 18:47
  • @Rob Maybe it's just me being difficult. My main gripe with that solution is that you can't strictly enforce protocol conformance. If MyViewSubclass has a delegate of type `MyViewDelegate`, there is no way to ensure that `(delegate as? MyViewSubclassDelegate)?.someOtherFunc()` will ever execute. The view could be assigned the superclass instead, and the optional chain would silently fail. You could also check if it responds to the selector, but then you've got a runtime constraint, rather than compile-time. – Andrew Gotow Aug 05 '15 at 19:03

1 Answers1

6

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044