[swift-evolution] [Proposal] Make optional protocol methods first class citizens

Douglas Gregor dgregor at apple.com
Mon Apr 4 11:34:24 CDT 2016


> On Apr 1, 2016, at 5:37 PM, Brent Royal-Gordon via swift-evolution <swift-evolution at swift.org> wrote:
> 
>> Protocol requirements with default (no-op) implementations already satisfy that design goal, no?
> 
> Kind of. If I may steelman* optional members for a moment...
> 
> In cases where a default implementation would do, the default implementation will usually also be the behavior you want for a nil instance, but there's no convenient way to share logic between the two. For example, consider this:
> 
> 	protocol UITableViewDelegate {
> 		...
> 		func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
> 	}
> 	extension UITableViewDelegate {
> 		func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
> 			return tableView.rowHeight
> 		}
> 	}
> 	
> 	class UITableView {
> 		...
> 		private func addRow(at indexPath: NSIndexPath) {
> 			...
> 			cell.size.height = delegate?.tableView(self, heightForRowAtIndexPath: indexPath) ?? rowHeight
> 			...
> 		}
> 		...
> 
> You have to duplicate the default logic both in the default implementation and at the call site, but there is no convenient way to share it—the extension method can't call into an expression at some call site, and contrarily the call site can't invoke the default logic from the extension.

Interesting point.

> 
> If the method were optional, then optional chaining would solve this problem for us:
> 
> 	protocol UITableViewDelegate {
> 		...
> 		optional func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
> 	}
> 	
> 	class UITableView {
> 		...
> 		private func addRow(at indexPath: NSIndexPath) {
> 			...
> 			cell.size.height = delegate?.tableView?(self, heightForRowAtIndexPath: indexPath) ?? rowHeight
> 			...
> 		}
> 		...
> 
> This way, there is only one source of default behavior: the call site.

It’s “each" call site, not “the” call site. If there are multiple call sites, we’d presumably want to factor out the default behavior computation anyway.

> I'm also concerned by the thought of just how many sub-protocols we might end up with. When I try to fully factor NSTableViewDelegate (as it currently exists in the headers), I end up with ten protocols:
> 
> 	NSTableViewDelegate
> 		- tableView:willDisplayCell:forTableColumn:row:
> 
> 	NSTableViewLayoutDelegate: NSTableViewDelegate
> 		- tableView:heightOfRow:
> 
> 	NSTableViewRowSelectionDelegate: NSTableViewDelegate
> 		- tableView:shouldSelectRow:
> 		- selectionShouldChangeInTableView:
> 		- tableViewSelectionIsChanging:
> 		- tableViewSelectionDidChange:
> 		- tableView:shouldTrackCell:forTableColumn:row: (10.5)
> 		- tableView:selectionIndexesForProposedSelection: (10.5)
> 
> 	NSTableViewTypeSelectDelegate: NSTableViewDelegate (10.5)
> 		- tableView:typeSelectStringForTableColumn:row:
> 		- tableView:nextTypeSelectMatchFromRow:toRow:forString:
> 		- tableView:shouldTypeSelectForEvent:withCurrentSearchString:
> 
> 	NSTableViewToolTipDelegate: NSTableViewDelegate
> 		- tableView:toolTipForCell:rect:tableColumn:row:mouseLocation:
> 
> 	NSTableViewColumnDelegate: NSTableViewDelegate
> 		- tableView:shouldEditTableColumn:row:
> 		- tableView:shouldSelectTableColumn:
> 		- tableView:mouseDownInHeaderOfTableColumn:
> 		- tableView:didClickTableColumn:
> 		- tableView:didDragTableColumn:
> 		- tableViewColumnDidMove:
> 		- tableViewColumnDidResize:
> 		- tableView:sizeToFitWidthOfColumn: (10.6)
> 		- tableView:shouldReorderColumn:toColumn: (10.6)
> 
> 	NSTableViewCellExpansionDelegate: NSTableViewDelegate (10.5)
> 		- tableView:shouldShowCellExpansionForTableColumn:row:
> 	
> 	NSTableViewCustomCellDelegate: NSTableViewDelegate (10.5)
> 		- tableView:dataCellForTableColumn:row:
> 		- tableView:isGroupRow:
> 
> 	NSTableViewCellViewDelegate: NSTableViewDelegate (10.7)
> 		- tableView:viewForTableColumn:row:
> 
> 	NSTableViewRowViewDelegate: NSTableViewDelegate (10.7)
> 		- tableView:rowViewForRow:
> 		- tableView:didAddRowView:forRow:
> 		- tableView:didRemoveRowView:forRow:
> 		- tableView:rowActionsForRow:edge: (10.11)
> 
> Some of these are probably unnecessary; they could be merged into NSTableViewDelegate and given default implementations. But at least a few of them would be very much needed. Would users be able to navigate this mess? Would they discover the features tucked away in sub-protocols? I'm just not sure.

This is what concerns me most. Delegate protocols tend to grow large over time (for good reason), and having a large family of related protocols is hard to navigate.

	- Doug



More information about the swift-evolution mailing list