Brent has a problem:
Swift protocols: http://t.co/njtgWljyy9
— Brent Simmons (@brentsimmons) July 19, 2015
//platform.twitter.com/widgets.js
His problem – protocols are seemingly messing up with mojo. It happens, and Equatable
with its Self
requirement can be quite annoying at times. However, his problem, at least as I understand it, is easily solvable, albeit with a bunch of code.
The key things to note about Brent's problem is that the Folder
and the Feed
are tightly coupled together; they have a strong type relationship together. So, we can use generics to help with this:
protocol Feed {
var url: String {get}
}
protocol Folder {
typealias FeedType
var feeds: [FeedType] {get}
func addFeeds(feedsToAdd: [FeedType])
}
class LocalFeed: Feed, Equatable {
var url: String
init(url: String) {
self.url = url
}
}
func ==(lhs: LocalFeed, rhs: LocalFeed) -> Bool {
return lhs.url == rhs.url
}
class LocalFolder: Folder {
var feeds = [LocalFeed]()
func addFeeds(feedsToAdd: [LocalFeed]) {
for oneFeed in feedsToAdd {
if !feeds.contains(oneFeed) {
feeds += [oneFeed]
}
}
}
}
Basically, move Equatable
to the type LocalFeed
and every other feed type, and create a typealias
on the Folder
protocol so a specific type of Feed
can be used.
We can go one step further, but we will see a slight problem when we do:
protocol Feed {
var url: String {get}
}
protocol Folder {
typealias FeedType : Equatable
var feeds: [FeedType] { get set }
mutating func addFeeds(feedsToAdd: [FeedType])
}
extension Folder {
mutating func addFeeds(feedsToAdd: [FeedType]) {
for oneFeed in feedsToAdd {
if !feeds.contains(oneFeed) {
feeds.append(oneFeed)
}
}
}
}
class LocalFeed: Feed, Equatable {
var url: String
init(url: String) {
self.url = url
}
}
func ==(lhs: LocalFeed, rhs: LocalFeed) -> Bool {
return lhs.url == rhs.url
}
class LocalFolder: Folder {
var feeds = [LocalFeed]()
}
Now all Folder
implementations get the addFeeds
functionality for free. BUT, we have to clearly mark what could be potentially mutating. The big side-effect of this is that feeds
becomes mutable outside of addFeeds
, which is not good.
The way to fix this, or the only way I know how to fix this, is to create a protocol, that by convention, people agree not to use. Something like this:
protocol Feed {
var url: String {get}
}
protocol Folder : _Folder {
typealias FeedType : Equatable
var feeds: [FeedType] { get }
mutating func addFeeds(feedsToAdd: [FeedType])
}
protocol _Folder {
typealias FeedType : Equatable
var _feedStore: [FeedType] { get set }
}
extension Folder {
mutating func addFeeds(feedsToAdd: [FeedType]) {
for oneFeed in feedsToAdd {
if !feeds.contains(oneFeed) {
_feedStore.append(oneFeed)
}
}
}
}
class LocalFeed : Feed, Equatable {
var url: String
init(url: String) {
self.url = url
}
}
func ==<T : Feed>(lhs: T, rhs: T) -> Bool {
return lhs.url == rhs.url
}
class LocalFolder : Folder {
var _feedStore : [LocalFeed] = []
var feeds: [LocalFeed] { return _feedStore }
}
The _Folder
protocol hides the mutation exposure of _feedStore
. Oh, and while we're at it, lets change ==
to be generic and work for any type of Feed
by default by comparing their respective url
members.
Anyhow, hope that helps somewhat.