/ Swift

Swift blog: Protocols are not always the way to go

I keep forcing myself to use protocols more and more when coding Swift.

Today's issue came from the following scenario:

I have a (static) table view with 4 prototype cells defined on Interface Builder. Each cell has its own identifier and UITableViewCell subclass assigned to it.

The requirement: each of cells must have a specific property from where it will automatically populate its fields:

var foo: Foo? {
    didSet {
        barTitleLabel.text = bar!.title
    }
}

Since I was quickly prototyping how everything would work, I had the following mess on my UITableViewDataSource implementation:


func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
	return 4
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
	switch indexPath.row {
	case 0:
		let cell = tableViewdequeueReusableCellWithIdentifier("FirstCell", forIndexPath: indexPath) as! FristCell
		cell.foo = currentFoo
		return cell
		
	case 1:
		let cell = tableViewdequeueReusableCellWithIdentifier("SecondCell", forIndexPath: indexPath) as! SecondCell
		cell.foo = currentFoo
		return cell

	case 2:
		let cell = tableViewdequeueReusableCellWithIdentifier("ThirdCell", forIndexPath: indexPath) as! ThirdCell
		cell.foo = currentFoo
		return cell		
		
	case 3:
		let cell = tableViewdequeueReusableCellWithIdentifier("FourthCell", forIndexPath: indexPath) as! FourthCell
		cell.foo = currentFoo
		return cell
		
	default: return UITableViewCell()
	}
}

So, there's an obvious issue. Lots and lots of code duplication. If you pay close attention, the only thing that's really changing is the identifier and the type, on each case everything is the same. Also, having that default clause there really bothers me.

Lets put protocols to good use.

Fixing the issue

I thought that I could map each String identifier with a unique value that identified that cell. Since each specific cell will be on the same spot every time, that was easy. I just created an enum:

enum Cell: Int {
	case FirstCell = 0
	case SecondCell
	case ThirdCell
	case FourthCell
}

extension Cell: CustomStringConvertible {
	var description: String {
		swith self {
			case .FirstCell: return "FirtCell"
			case .SecondCell: return "SecondCell"
			case .ThirdCell: return "ThirdCell"
			case .FourthCell: return "FourthCell"
		}
	}
}

It simplified the cell creation... kind of:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
	let cellType = Cell(rawValue: indexPath.row)! // the actual implementation does not use an explicitly unwrapped optional.
	
	let cell = tableView.dequeueReusableCellWithIdentifier(cellType.description, forIndexPath: indexPath)
	
	switch indexPath.row {
		case 0:
			var aCell = (cell as! FirstCell)
			aCell.foo = currentFoo
			return aCell
			
		case 1:
			var aCell = (cell as! SecondCell)
			aCell.foo = currentFoo
			return aCell
			
		// And so on...
		
	}
}

Not quite what I was hoping for, actually. Then I thought that it would be cool if, just as Cell can return the identifier for the appropriate row, it returned the type of the cell for the appropriate row. So I dug into it.

First approach

My first approach was just to have a variable that gave me the Type I was expecting:

extension Cell {
	var type: AnyClass {
		case .FirstCell: return FirstCell.self
		case .SecondCell: return SecondCell.self
		case .ThirdCell: return ThirdCell.self
		case .FourthCell: return FourthCell.self
	}
}

Everything seemed ok — it compiled correctly, but when I tried to implement it:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
	let cellType = Cell(rawValue: indexPath.row)! // the actual implementation does not use an explicitly unwrapped optional.
	
	let cell = tableView.dequeueReusableCellWithIdentifier(cellType.description, forIndexPath: indexPath) as! cellType.type // ERROR!!!
	
	// ...
}

Which made Xcode spit thie error cellType is not a type. Turns out the type casting operator can't process statements on its right side. So that's an issue, that automatically discarded my option of making Cell generic on subclasses of UITableViewCell [1].

Second approach

After a lot of thinking about the most elegant way to solve this, I came up with the following: create a protocol that all my cells conformed to, so that way I don't have to worry about the cell's actual type:

protocol Fooable {
	var foo: Foo? { get set }
}

"Let's use the magic of inheritance, and have every UITableViewCell subclass be Fooable," I thought to myself:

extension UITableViewCell: Fooable {
	var foo: Foo?  // ERROR: “Extensions may not contain stored properties.”
}

Ok, so... "Extensions may not contain stored properties," huh? Let's change the protocol, then:

protocol Fooable {
	func setFoo(foo: Foo)
}

extension UITableViewCell: Fooable {
	func setFoo(foo: Foo) {}
}

It sounded great on my head, but you can see the issue here... It would require every UITableViewCell subclass to override the setFoo: method, and also, it would require them to define a custom var foo: Foo?, which, by the way, is not cool because that requirement is not expreesed anywhere. Ah, and also, this polutes UITableViewCell on the whole app context.

"Aha!" moment! Create a UITableViewCell subclass. That way, I could define te foo var on the protocol itself, and avoid poluting all UITableViewCell:

protocol Fooable {
	var foo: Foo? { get set }
}

class BaseCell: UITableViewCell, Fooable {	var foo: Foo?
}

class FirstCell: BaseCell {
	override var foo: Foo? {
		didSet {
			// ...
		}
	}
}

// And so on...

So, my UITableViewDataSource now looks something like this:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
	let cellType = CellType(rawValue: indexPath.row)!
        
    let cell = tableView.dequeueReusableCellWithIdentifier(cellType.description, forIndexPath: indexPath) as! BaseCell
    cell.foo = currentFoo
        
    return cell
}

Ahhh... much cleaner!

And then it hit me! I don't need a protocol for this! So, 10 seconds later, Fooable disappeared and everything still worked correctly.

What I learned

  1. Protocol-Oriented-Programming is so fun and powerful that may make you forget certain aspects of Object-Oriented-Programming. Although it is useful, it is not always the way to go, as I found out today.

  2. Remember that Cocoa's architecture is built with OOP, so POP may bite you in the ass if you try to force it.

  3. I'd still use my original approach with the enum if it was possible. This is related to what Samuel Giddins wishes comes with Swift 3.0. I certainly wish it too.

  4. This was a fun experiment!

  5. I found this answer on StackOverflow. However, I didn't like that this approach depends creating a class from a string. I want strings the least possible, I want the actual type.


  1. This has been reported to Apple as a suggestion: rdar://23622714 ↩︎