inessential by Brent Simmons

Swift Diary #7: Protocols, Arrays, and Casting

I mentioned before that I’m making heavy use of protocols in my new apps.

Here’s what got me today:

Let’s pretend I’m writing an email client. (This is an example I use on my blog for historical reasons, and should not be taken as indicative.)

I have a tree of Node objects. Each Node has a represented object, which is a data model object. There’s a NodeRepresentedObject protocol.

There are also more-specific protocols. EmailMessage, for instance, is a protocol because I might need different concrete classes for different types of accounts (Gmail, IMAP, POP, etc.).

My class Mailbox has its internal array of email messages. It also has a children variable which is part of the NodeRepresentedObject protocol. Here’s the relevant code:

protocol NodeRepresentedObject : class {
  var children: [NodeRepresentedObject]? {get}
}

protocol EmailMessage : NodeRepresentedObject {
  // stuff here…
}

class Mailbox : NodeRepresentedObject {
  var messages = \[EmailMessage]()
  var children: [NodeRepresentedObject]? {
    get {
      return messages
    }
  }
}

The error is: 'EmailMessage' is not identical to 'NodeRepresentedObject'.

That’s true: it’s not. But an EmailMessage is a NodeRepresentedObject, so it ought to work just fine.

I tried this:

return messages as [NodeRepresentedObject]

The error: '[EmailMessage]' is not convertible to '[NodeRepresentedObject]'.

Why not? Again — it is a NodeRepresentedObject.

The best way I’ve found to deal with this is to map it.

return messages.map { return $0 as NodeRepresentedObject }

That seems crazy. I’m using map to create an array whose members are absolutely identical to the original array. And if it would let me do that, why not allow my original solution? (Or at least the second solution?)

Update 5:30 pm: Filed as rdar://22109003.