Andy Ibanez
Andy Ibanez

Reputation: 12254

What's the correct way to implement default properties when doing default implementation of protocols in Swift?

I am truly fascinated by the concept of Protocol-Oriented Programming in Swift, and because of that, I'm moving an old project I created last year (which was originally an OOP framework) to POP.

At this stage, the problems I'm having could be because I'm either understanding POP incorrectly, or the Swift 2.0 Betas don't have everything to create a truly Protocol-Oriented framework (not very likely - If anything I might be misunderstanding certain aspects of POP).

Protocol-Oriented Programming is a whole new programming paradigm introduced to the world less than a month ago, so there is not much written content about it (the only tutorial I found on the topic doesn't address the problem I'm having, and the WWDC video didn't either).

Anyway, to the point: I am either doing something wrong here, or one of the downsides of Protocol-Oriented Programming is that you are bound to repeat a lot of code. Case in point:

I have the following protocol that has many properties and also complies with the Equatable protocol:

protocol MediaType : Equatable {

    /// MARK: - Properties

    /// The ID of the media, as assigned by MyAnimeList.
    var ID: Int { get }

    /// The title of the media.
    var title: String { get }

    /// Other titles by which this anime may be commonly known (only available in details requests).
    var otherTitles: (synonyms: [String], english: [String], japanese: [String])? { get }

    /// The global ranking of the title (only available in anime details requests).
    var rank: Int? { get }

    /// Rank of the title based on popularity (number of people adding title to the list) (only available in details requests).
    var popularityRank: Int? { get }

    /// URL to a representative image of the title. Usually a "cover" image.
    var imageURL: String { get }

    /// A list of adaptations of this media, or other media on which this media is based (only available in details requests).
    var adaptations: Relationships { get }

    /// The user's rating of the media.
    var memberScore: Float { get }

    /// Number of MyAnimeList members that that added the title to their list (only available in details requests).
    var membersCount: Int? { get }

    /// The number of MyAnimeList members that have this title on their favorites list (only available in details requests).
    var favoritedCount: Int? { get }

    /// A short HTML-formatted description of the media.
    var synopsis: String { get }

    /// A list of genres for this title (only available in details requests).
    var genres: [String]? { get }

    /// Popular tags for the title as assigned by MyAnimeList members (only available in details requests).
    var tags: [String] { get }
}

In the original version of my framework, this protocol was a class called Media, and two other classes inherited from it. So they got all those properties for free.

But it doesn't look like I can give my objects that comply with that protocol a default implementation (namely, getters) for those properties?

The first thing I tried, which is to simply give the protocol to my struct declaration, failed, which was to be expected as I wasn't providing any implementation for the properties:

struct Anime : MediaType {

    /// MARK: - MediaType

}

/// Compares two Anime_ objects. Two Anime_ objects are considered equal when they have the same ID and title.
func ==(lhs: Anime, rhs: Anime) -> Bool {
    return (lhs.ID == rhs.ID) && (lhs.title == rhs.title)
}

This fails with:

Type 'Anime' does not conform to protocol 'MediaType'

My next attempt was to write an extension for MediaType, and throw the properties there:

extension MediaType {
    /// The ID of the media, as assigned by MyAnimeList.
    let ID: Int

    /// The title of the media.
    let title: String

    /// Other titles by which this anime may be commonly known (only available in details requests).
    let otherTitles: (synonyms: [String], english: [String], japanese: [String])?

    /// The global ranking of the title (only available in anime details requests).
    let rank: Int?

    /// Rank of the title based on popularity (number of people adding title to the list) (only available in details requests).
    let popularityRank: Int?

    /// URL to a representative image of the title. Usually a "cover" image.
    let imageURL: String

    /// A list of adaptations of this media, or other media on which this media is based (only available in details requests).
    let adaptations: Relationships

    /// The user's rating of the media.
    let memberScore: Float

    /// Number of MyAnimeList members that that added the title to their list (only available in details requests).
    let membersCount: Int?

    /// The number of MyAnimeList members that have this title on their favorites list (only available in details requests).
    let favoritedCount: Int?

    /// A short HTML-formatted description of the media.
    let synopsis: String

    /// A list of genres for this title (only available in details requests).
    let genres: [String]?

    /// Popular tags for the title as assigned by MyAnimeList members (only available in details requests).
    let tags: [String]
}

This didn't work:

Extensions may not contain stored properties.

And it had the one downside that I really didn't like: I was already duplicating code, by copying the properties of the protocol into the extension.

So in the end, I could never get my properties to be "spread" to the objects conforming to the protocol, so I ended up adding the properties to the Anime struct.

struct Anime : MediaType {

    /// MARK: - MediaType

    /// The ID of the media, as assigned by MyAnimeList.
    let ID: Int

    /// The title of the media.
    let title: String

    /// Other titles by which this anime may be commonly known (only available in details requests).
    let otherTitles: (synonyms: [String], english: [String], japanese: [String])?

    /// The global ranking of the title (only available in anime details requests).
    let rank: Int?

    /// Rank of the title based on popularity (number of people adding title to the list) (only available in details requests).
    let popularityRank: Int?

    /// URL to a representative image of the title. Usually a "cover" image.
    let imageURL: String

    /// A list of adaptations of this media, or other media on which this media is based (only available in details requests).
    let adaptations: Relationships

    /// The user's rating of the media.
    let memberScore: Float

    /// Number of MyAnimeList members that that added the title to their list (only available in details requests).
    let membersCount: Int?

    /// The number of MyAnimeList members that have this title on their favorites list (only available in details requests).
    let favoritedCount: Int?

    /// A short HTML-formatted description of the media.
    let synopsis: String

    /// A list of genres for this title (only available in details requests).
    let genres: [String]?

    /// Popular tags for the title as assigned by MyAnimeList members (only available in details requests).
    let tags: [String]

    /// MARK: - Anime


}

And this seemed to work. But now I have my properties in both MediaType and Anime. In OOP, you avoid the duplicate code by subclassing.

So I repeat my question here: Am I misunderstanding Protocol-Oriented Programming, or is the downside of POP that you have to copy-and paste your protocol-specific logic whenever you make a struct/class/enum comply with it?

Upvotes: 2

Views: 1589

Answers (2)

Andy Ibanez
Andy Ibanez

Reputation: 12254

It's been a few weeks since I posted this, but I believe what Aaron Brager said is true.

While Protocol-Oriented-Programming is rather new in itself, the idea of protocols has been present in Objective-C for a long time, they are in Swift, and they have their variants in languages such as Java. Due to the nature of protocols and extensions, it looks like doing default implementations of properties is not possible, as extensions won't allow you to set non-computed properties in them.

Upvotes: 4

Code Different
Code Different

Reputation: 93161

That is because your Anime struct doesn't implement all properties from the MediaType protocol. Here's a minimized example of how you can do it:

protocol MediaType : Equatable {
    var ID: Int { get }
    var title: String { get }
}

struct Anime : MediaType {
    // Implement the MediaType protocol
    var ID : Int
    var title : String
}

/// Compares two Anime_ objects. Two Anime_ objects are considered equal when they have the same ID and title.
func ==(lhs: Anime, rhs: Anime) -> Bool {
    return (lhs.ID == rhs.ID) && (lhs.title == rhs.title)
}

let x = Anime(ID: 1, title: "title 1")
let y = Anime(ID: 2, title: "title 2")
let z = Anime(ID: 1, title: "title 1")

println(x == y) // false
println(x == z) // true

One curiosity though: in the protocol, why are you declaring all properties as read-only?

Upvotes: 1

Related Questions