Brent's feeds and folders problem
Brent Simmons described a problem he faced when adopting protocol-oriented programming with Swift 2.0. (To follow along, please read his post in full before continuing with this post.)
The crux of the problem is that equality between two values of a custom
type is not implicit in Swift. So, with a custom type
Feed, it’s not
possible out of the box to check if an array of feeds contains a
particular feed - we first need to tell Swift what it means to say two
feeds are the same.
The quick Objective-C version that Brent wrote does have a similar problem as well, though it’s less obvious.
The Objective-C version
In the Objective-C version of the project, I’m assuming both
Folder would be defined as Objective-C protocols, and we’d have
NSObject-based Objective-C classes
conforming to those protocols respectively.
Brent’s Objective-C version of
addFeeds (in say
looks like this:
self.feeds contains the feeds that are already in the local folder.
feedsToAdd contains the feeds that we’d like to add to the folder.
It’s likely that the objects in
feedsToAdd were created at a different
point in time than the objects already in
self.feeds. The default
equality checking in
NSObject is to check for the equality of
addFeeds wouldn’t work correctly if the feed urls are the
same, but the object addresses are different.
One way to fix this problem is to override
to check the equality of the feed urls rather than memory addresses.
This would override the implicit equality checking with the equality
checking that we really want.
(Update 22/Jul/2015: Brent clarified that pointer equality was really what he wanted.)
Also, I think the concept of a feed is more like a struct than a class.
But there’s no way for a value type to conform to a protocol in
Objective-C, so we are forced to make
LocalFeed a class here.
This is how I would model this scenario in Swift.
A feed is anything that has
url as a gettable property.
LocalFeed is a value type conforming to the
We need to be able to check if a feed exists in an array, so we should
define what equality for feeds means. We do that by making the
protocol inherit from the
Equatable protocol requires that we implement a function with
func ==(lhs: Self, rhs: Self) -> Bool. The
refers to the actual type conforming to the protocol (like
that we’re checking for equality. So, a function signature of
==(lhs: Feed, rhs: Feed) -> Bool will not help us conform to
Feed is a protocol, not a type.
We should write a function that can take two values of a particular
type, where that type conforms to the
Feed protocol. We can do that
A folder contains a bunch of feeds. We can have different types of folders, and they would contain the corresponding types of feeds. For example, a LocalFolder would contain a bunch of LocalFeeds, and a FeedlyFolder would contain a bunch of FeedlyFeeds. It’s not possible for a FeedlyFolder to contain a FeedBinFeed.
To represent a bunch of feeds in a folder, let’s use an array.
To represent an array of feeds, we might write
[Feed]. But if we
really think about it, that’s not correct because
Feed is not a type -
it’s a protocol. What we want is an array of values of a certain type,
where that type is a type that conforms to the
We need a
typealias to represent this. When we say
FeedType: Feed, we create a placeholder type called
conforms to the
Feed protocol. We can then proceed to use
in further declarations in our definition of a folder.
Note that the type of the
feeds property and the type of
in the signature of
addFeeds are now tied to each other. If you create
a type that conforms to the
Folder protocol, these types must be the
same types. They cannot be two different types, even if both of them
conform to the
So, when we create a
LocalFolder we need to use a specific type
conforming to the
Feed protocol, rather than just saying
We can use it like this:
You can see the complete code here.
Compared to Brent’s original version, the only significant additional
code is the
== function. But I would argue that likewise, we needed an
isEqual: in Objective-C to make it work correctly.
I’d love to hear your feedback on this, especially Brent’s. You can find me on Twitter @roopeshchander.
(Update 22/Jul/2015: Got some good feedback on Twitter, including Brent’s.)