1

I would like to create my models for my iOS app using struct as I understand it is the appropriate way to do things. I have the following:

protocol Exercise: Identifiable {
    var id: UUID { get }
    var name: String { get }
    var type: ExerciseType { get }

}

enum ExerciseType {
    case duration
    case reps
    case rest
}

extension Exercise {
    var id: UUID { UUID() }
}

struct DurationExercise: Exercise {
    let name: String
    let durationInSeconds: Int
    let type: ExerciseType = .duration
    
}

struct RepetionsExercise: Exercise {
    let name: String
    let reps: Int
    let type: ExerciseType = .reps
}

struct RestExercise: Exercise {
    let name: String
    let durationInSeconds: Int
    let type: ExerciseType = .rest
}
  • Is this extending the proper way to initialize the id: UUID using protocol + structs?

  • Is there a way where I don't have to specify the name: String in every struct that inherits Exercise?

  • Or if I want to do something like I should just use class?

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Ivan Santos
  • 624
  • 1
  • 8
  • 21
  • What do you mean by initialize just once? One UUID for all structs that conform to that protocol? – Leo Dabus Jan 22 '22 at 21:09
  • 3
    As a computed property it will generate a new UUID every time you call that property. – Leo Dabus Jan 22 '22 at 21:11
  • No need to create a very similar struct with so many common properties if you have already an enumeration for exercise types – Leo Dabus Jan 22 '22 at 21:14
  • 3
    I agree. This approach is going to come back and bite you because while all of your types implement `Excercise` they are not co-variant. Much better to have a single `Excercise` struct with optional `reps` and `duration` properties. – Paulw11 Jan 22 '22 at 21:17
  • 1
    `id` is already a required property of `Identifiable` so there is no point to have it in your own protocol. – Joakim Danielson Jan 22 '22 at 21:19
  • I am trying to achieve where `let type = DURATION` would always contain `duration` without having to check if it is `nil`. Otherwise, I am fine creating struct `Exercise` that contains optional `reps` and `duration`. – Ivan Santos Jan 22 '22 at 21:26
  • 1
    You will need to write conditional code either way. Either you have to conditionally unwrap `reps` and `duration` or you are going to have to conditionally downcast variables of type `Exercise` to concrete types. And then what happens when you decide to create a new type of exercise `TimedRepertitionsExercise`? If have optionals you don't have to change much. If you are using different structs then you now have to test for a new kind of struct. Structs and protocols don't have inheritance and it is a mistake to treat them like they do. – Paulw11 Jan 22 '22 at 21:34
  • 1
    If you continue with this approach I foresee [Protocol can only be used as a generic constraint because it has Self or associated type requirements](https://stackoverflow.com/questions/36348061/protocol-can-only-be-used-as-a-generic-constraint-because-it-has-self-or-associa) in your future – Paulw11 Jan 22 '22 at 21:36
  • Yep, I got the that error. It makes sense what you are saying. I need to wrap my head what struct is exactly, as clearly I do not have a good understanding of it. Thanks for feedback. I'll be using your recommended approach. – Ivan Santos Jan 22 '22 at 21:38

1 Answers1

3

Considering that you have a fixed set of exercise types, it would be simpler to eliminate the protocol entirely:

struct Exercise: Identifiable {
    var id: UUID
    var name: String
    var type: ExerciseType
    
    enum ExerciseType {
        case duration(TimeInterval)
        case reps(Int)
        case rest(TimeInterval)
    }
}

If you need multiple associated values for one of the types, you might choose to introduce another struct to hold the multiple values. Example:

struct Exercise: Identifiable {
    var id: UUID
    var name: String
    var type: ExerciseType
    
    enum ExerciseType {
        case duration(TimeInterval)
        case reps(Reps)
        case rest(TimeInterval)
    }
    
    struct Reps {
        var sets: Int
        var repsPerSet: Int
    }
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • I was unaware that I could set enum in this way. Is there a term for defining enum value like so `enum ExerciseType { case duration(TimeInterval) }`? I will give this a try. – Ivan Santos Jan 22 '22 at 22:05
  • 2
    It's called an “associated value”. You can read about it in [“Enumerations” in *The Swift Programming Lanugage*](https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html). – rob mayoff Jan 22 '22 at 22:08
  • 1
    More generally, what Swift calls an `enum` is called a “sum type” in the study of programming languages, and is one half of what is called ”algebraic data types” (the other half being a ”product type” which in Swift is a `struct`). Sum types are an essential part of a programming philosophy called “Domain-Driven Design”. The motto of domain-driven design is “make illegal states unrepresentable” (or “make impossible states impossible”). I highly recommend learning about it, because it will make you a better programmer. – rob mayoff Jan 22 '22 at 22:15