koen
koen

Reputation: 5729

Swift protocol with properties that are not always used

This is a follow-up to this question: Can I have a Swift protocol without functions

Suppose I want to add more properties to my protocol:

protocol Nameable {
     var name: String {get}
     var fullName: String: {get}
     var nickName: String: {get}
}

However, not every struct that conforms to this protocol may have a fullName and/or nickName. How do I go about this? Can I make these two properties somehow optional? Or maybe I need three separate protocols? Or do I just add them to each struct, but leave them empty, like this:

struct Person : Nameable {
    let name: String
    let fullName: String
    let nickName: String
    let age: Int
    // other properties of a Person
}

let person = Person(name: "Steve", fullName: "", nickName: "Stevie", age: 21)

That compiles and works, but I don't know if this is the 'correct' approach?

Upvotes: 4

Views: 2995

Answers (2)

Hamish
Hamish

Reputation: 80781

Unlike in Objective-C, you cannot define optional protocol requirements in pure Swift. Types that conform to protocols must adopt all the requirements specified.

One potential way of allowing for optional property requirements is defining them as optionals, with a default implementation of a computed property that just returns nil.

protocol Nameable {
    var name : String? { get }
    var fullName : String? { get }
    var nickname : String? { get }
}

extension Nameable {
    var name : String? { return nil }
    var fullName : String? { return nil }
    var nickname : String? { return nil }
}

struct Person : Nameable {

    // Person now has the option not to implement the protocol requirements,
    // as they have default implementations that return nil

    // What's cool is you can implement the optional typed property requirements with
    // non-optional properties – as this doesn't break the contract with the protocol.
    var name : String
}

let p = Person(name: "Allan")
print(p.name) // Allan

However the downside to this approach is that you potentially pollute conforming types with properties that they don't implement (fullName & nickName in this case).

Therefore if it makes no logical sense for a type to have these properties (say you wanted to conform City to Nameable – but cities don't (really) have nicknames), you shouldn't be conforming it to Nameable.

A much more flexible solution, as you say, would be to define multiple protocols in order to define these requirements. That way, types can choose exactly what requirements they want to implement.

protocol Nameable {
    var name : String { get }
}

protocol FullNameable {
    var fullName : String { get }
}

protocol NickNameable {
    // Even types that conform to NickNameable may have instances without nicknames.
    var nickname : String? { get }
}

// A City only needs a name, not a fullname or nickname
struct City : Nameable {
    var name : String
}

let london = City(name: "London")

// Person can have a name, full-name or nickname
struct Person : Nameable, FullNameable, NickNameable {
    var name : String
    var fullName: String
    var nickname: String?
}

let allan = Person(name: "Allan", fullName: "Allan Doe", nickname: "Al")

You could even use protocol composition in order to define a typealias to represent all three of these protocols for convenience, for example:

typealias CombinedNameable = Nameable & FullNameable & NickNameable

struct Person : CombinedNameable {
    var name : String
    var fullName: String
    var nickname: String?
}

Upvotes: 4

Jans
Jans

Reputation: 11250

You can give a default implementation to these property using protocol extension and override the property in classes/structs where actually you needed

extension Nameable{
   var fullName: String{
      return "NoFullName"
   }

  var nickName: String{
     return "NoNickName"
  }
}
struct Foo : Nameable{
   var name: String
}

Upvotes: 1

Related Questions