Defining protocols for certain class types only?

I'd like to define a protocol to be implemented by UITableViewCell kinds of types only and have the compiler enforce this requirement for me:

protocol ProfileTableViewCell: UITableViewCell {
    var photoView: UIIimageView? { get }
    var nameLabel: UILabel? { get }
}

class DataSource: NSObject, UITableViewDataSource {
    let cells = [ProfileTableViewCell]()
   // ...implementation continues...
}

Right now, I get compiler errors. To work around this, I have to do things like this:

protocol PersonCell: class { // `class` here is compiled as `AnyObject`
    var photoView: UIIimageView? { get }
    var nameLabel: UILabel? { get }
}

class DataSource: NSObject, UITableViewDataSource {
    typealias Cell = UITableViewCell & PersonCell
    let cells = [Cell]()
   // ...implementation continues...
}

How hard would it be for a newbie to dive into the compiler source code and make it use the class specified? In this case, UITableViewCell rather than AnyObject?

1 Like

I'm not quite sure what you want here. Are you trying to extend all UITableViewCells? Only one?

Just a reminder that you can't use a custom toolchain for building iOS apps for the App Store. For a change like this I'd recommend shying away from modifying the compiler.

No, I'm defining an interface that UITableViewCell or a subclass of it can implement.

Thanks! This is not what I meant. What I meant to suggest was pitching and submitting.

The feature you ask is not in the language yet β€” anyone can conform to a protocol. The & syntax on downstream users is how you limit those conformers to use only the right class.

cc @Slava_Pestov

I think this kind of constraint is in the SR-6001 bug report?

The protocol PersonCell: class example is deprecated in the SE-0156 proposal, but not in the compiler.

At the moment, there's only a warning if class, AnyObject are both used: apple/swift#11989.

I don't know why people are saying this isn't possible - it is, and I use it all the time. It's especially helpful to annotate that certain protocols are expected to be conformed to by UIViews, for example.

The magic words you're looking for are:

protocol ProfileTableViewCell: class where Self: UITableViewCell {
}

Note:
Technically, the : class part shouldn't be required, but including it helps counter compiler bugs. If you don't include it, sometimes the compiler forgets that these objects are classes and doesn't retain them, and last time I checked, it wouldn't compile in release builds if you mutated a property on a let instance. Include the class constraint, and you'll be fine.

11 Likes

I didn't know about this! In a Playground (Xcode 9.2) it works for me, even without the class (which the compiler specifies as redundant).

Yes, that's exactly the kind of constraint in the bug report. Slava's latest response was that this wan't implemented yet, which I've assumed means that any apparent support for this is either partial or incidental.

I was very much surprised that protocol ProfileTableViewCell : AnyObject where Self : UITableViewCell this works. :face_with_raised_eyebrow:

Just tried this and it doesn't work.

protocol HeaderViewing where Self: UIView {
   func set(title: String)
}

let myView: HeaderViewing = MyView()
myView.set(title: "Hello")
...
...
...
self.myView.removeFromSuperview()
/** Value of type 'HeaderViewing' has no member 'removeFromSuperview' */

Possibly same root issue as [SR-6977] NSObject inheritance causes: Error non-nominal type does not support explicit initialization Β· Issue #49525 Β· apple/swift Β· GitHub

1 Like

The actual root issue is SR-6001: class constrained protocols haven't actually been implemented yet.

Did you try it with the : class constraint?

Of course Nobody1707 is correct and the root cause is that this feature is technically unimplemented. This is a sort of hack/coincidence/oversight.

Hey, I know, a lot of time left :)
Anyway, here is the solution for the Swift 4.2: if you define your myView as UIView that confirmed your HeaderViewing protocol, then it will work:

protocol HeaderViewing where Self: UIView {
   func set(title: String)
}

let myView: UIView & HeaderViewing = MyView()
myView.set(title: "Hello")

self.myView.removeFromSuperview()

The superclass constraint is actually redundant -– HeaderViewing already requires every conforming type to be a subtype of UIView. Simply put, every HeaderViewing is also a UIView.

True in theory, but in reality for this variable definition:

let myView: HeaderViewing = MyView()

you can't call removeFromSuperview(). Compiler doesn't see UIView methods and properties "inside" HeaderViewing protocol with the UIView constraint.

I see, that was certainly a bug. Fortunately is has already been fixed in the upcoming Swift 5 release. This can be confirmed with the latest Xcode beta release.

2 Likes

Thanks! I edited my comment with a note that it's all about Swift 4.2

1 Like