Specified Protocol Conformances in Subclasses


(Rod Brown) #1

Hi everyone. I found something odd that seems baked into how Cocoa Touch does protocols, but Swift cannot model it:

@interface UIScrollView: UIView

@property (weak, nonatomic) id <UIScrollViewDelegate> delegate;

@end

@interface UITableView: UIScrollView

@property (weak, nonatomic) id <UITableViewDelegate> delegate;

@end

@protocol UITableViewDelegate: UIScrollViewDelegate
...
@end

Subclasses can further specify the conformance of a property’s protocol-conforming object to state a further type on that property. I tried to do something extremely similar today in Swift, and it failed, saying the protocols were different:

class SourceBar: UIScrollView {
  
  override var delegate: SourceBarDelegate? {
    get { return super.delegate as? SourceBarDelegate }
    set { super.delegate = newValue }
  }

}

@objc protocol SourceBarDelegate: UIScrollViewDelegate {
  func foo()
}

Considering the fact that the protocol SourceBarDelegate conforms to UIScrollViewDelegate, I can’t see why this override should fail. Can anyone enlighten me as to why this is a limitation in the language?

Thanks,

Rod


(Adrian Zubarev) #2

I kinda feel this is somehow related: http://discourse.natecook.com/t/pitch-return-a-subclass-for-a-protocol-method-without-the-need-for-an-associatedtype/2355

It would be really handy to be able to override any type A with another type B where the relationship is B : A.

···

--
Adrian Zubarev
Sent with Airmail

Am 10. März 2017 um 13:08:37, Rod Brown via swift-evolution (swift-evolution@swift.org) schrieb:

Hi everyone. I found something odd that seems baked into how Cocoa Touch does protocols, but Swift cannot model it:

@interface UIScrollView: UIView

@property (weak, nonatomic) id <UIScrollViewDelegate> delegate;

@end

@interface UITableView: UIScrollView

@property (weak, nonatomic) id <UITableViewDelegate> delegate;

@end

@protocol UITableViewDelegate: UIScrollViewDelegate
...
@end

Subclasses can further specify the conformance of a property’s protocol-conforming object to state a further type on that property. I tried to do something extremely similar today in Swift, and it failed, saying the protocols were different:

class SourceBar: UIScrollView {

override var delegate: SourceBarDelegate? {
get { return super.delegate as? SourceBarDelegate }
set { super.delegate = newValue }
}

}

@objc protocol SourceBarDelegate: UIScrollViewDelegate {
func foo()
}

Considering the fact that the protocol SourceBarDelegate conforms to UIScrollViewDelegate, I can’t see why this override should fail. Can anyone enlighten me as to why this is a limitation in the language?

Thanks,

Rod
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Rob Mayoff) #3

The problem here is that `UITableView`'s redefinition of `delegate` is not
type-safe. By casting a `UITableView` reference to a `UIScrollView`, you
set the `delegate` to something that doesn't conform to `UITableView`:

    @interface ViewController () <UIScrollViewDelegate>
    @end

    @implementation ViewController

    - (void)viewDidLoad {
        [super viewDidLoad];

        UITableView *tableView = [[UITableView alloc] init];

        // This line correctly generates a warning, because self isn't a
UITableViewDelegate:
        tableView.delegate = self;

        // This line generates no warning:
        ((UIScrollView *)tableView).delegate = self;
    }

    @end

···

On Fri, Mar 10, 2017 at 6:08 AM, Rod Brown via swift-evolution < swift-evolution@swift.org> wrote:

Hi everyone. I found something odd that seems baked into how Cocoa Touch
does protocols, but Swift cannot model it:

@interface UIScrollView: UIView

@property (weak, nonatomic) id <UIScrollViewDelegate> delegate;

@end

@interface UITableView: UIScrollView

@property (weak, nonatomic) id <UITableViewDelegate> delegate;

@end

@protocol UITableViewDelegate: UIScrollViewDelegate
...
@end


(Anton Zhilin) #4

Looks like a compiler bug, since it works with classes:

class Base {}class Derived : Base {}
class A {
    var x: Base? { return Base() }
}
class B : A {
    override var x: Derived? { return Derived() }
}


(David Waite) #5

The getter is covariant while the setter is contravariant. The result is read/write properties are invariant.

Objective-C basically considers declared types to be advisory (to generate compiler warnings). In reality, both delegate properties are of type ‘id’, with type issues surfacing at runtime.

-DW

···

On Mar 10, 2017, at 5:08 AM, Rod Brown via swift-evolution <swift-evolution@swift.org> wrote:

Hi everyone. I found something odd that seems baked into how Cocoa Touch does protocols, but Swift cannot model it:

@interface UIScrollView: UIView

@property (weak, nonatomic) id <UIScrollViewDelegate> delegate;

@end

@interface UITableView: UIScrollView

@property (weak, nonatomic) id <UITableViewDelegate> delegate;

@end

@protocol UITableViewDelegate: UIScrollViewDelegate
...
@end

Subclasses can further specify the conformance of a property’s protocol-conforming object to state a further type on that property. I tried to do something extremely similar today in Swift, and it failed, saying the protocols were different:

class SourceBar: UIScrollView {
  
  override var delegate: SourceBarDelegate? {
    get { return super.delegate as? SourceBarDelegate }
    set { super.delegate = newValue }
  }

}

@objc protocol SourceBarDelegate: UIScrollViewDelegate {
  func foo()
}

Considering the fact that the protocol SourceBarDelegate conforms to UIScrollViewDelegate, I can’t see why this override should fail. Can anyone enlighten me as to why this is a limitation in the language?

Thanks,

Rod
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Waite) #6

This works because your properties are read-only, thus allowed to be covariant. If you add a setter, the compiler will complain.

-DW

···

On Mar 10, 2017, at 5:33 AM, Anton Zhilin via swift-evolution <swift-evolution@swift.org> wrote:

Looks like a compiler bug, since it works with classes:

class Base {}
class Derived : Base {}

class A {
    var x: Base? { return Base() }
}

class B : A {
    override var x: Derived? { return Derived() }
}
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Rod Brown) #7

Hi Rob,

I think we could actually do that (and cause the same bug) with the derived classes Anton mentioned, which means the behaviour is inconsistent, despite the perhaps safety.

- Rod

···

On 11 Mar 2017, at 6:32 am, Rob Mayoff <mayoff@dqd.com> wrote:

On Fri, Mar 10, 2017 at 6:08 AM, Rod Brown via swift-evolution <swift-evolution@swift.org> wrote:
Hi everyone. I found something odd that seems baked into how Cocoa Touch does protocols, but Swift cannot model it:

@interface UIScrollView: UIView

@property (weak, nonatomic) id <UIScrollViewDelegate> delegate;

@end

@interface UITableView: UIScrollView

@property (weak, nonatomic) id <UITableViewDelegate> delegate;

@end

@protocol UITableViewDelegate: UIScrollViewDelegate
...
@end

The problem here is that `UITableView`'s redefinition of `delegate` is not type-safe. By casting a `UITableView` reference to a `UIScrollView`, you set the `delegate` to something that doesn't conform to `UITableView`:

    @interface ViewController () <UIScrollViewDelegate>
    @end

    @implementation ViewController

    - (void)viewDidLoad {
        [super viewDidLoad];

        UITableView *tableView = [[UITableView alloc] init];

        // This line correctly generates a warning, because self isn't a UITableViewDelegate:
        tableView.delegate = self;

        // This line generates no warning:
        ((UIScrollView *)tableView).delegate = self;
    }

    @end