简体   繁体   中英

How to declare different structs inside one array in Swift

Is it possible to create an array of different structs? My data structure looks like this:

enum MovementType: String, Codable {
    case WeightMovement
    case RepsMovement
}

struct Movement: Identifiable, Codable {
    let id: UUID = UUID()
    let name: String
    let type: MovementType
    let workouts: [WeightMovement, RepsMovement] ---> A solution for this line based on the Type above
}

struct WeightMovement: Identifiable, Codable {
    let id: UUID = UUID()
    let weight: Double
    let sets: Int
    let reps: Int
}

struct RepsMovement: Identifiable, Codable {
    let id: UUID = UUID()
    let sets: Int
    let reps: Int
    let seconds: Int
}

A brief example of what is should do: A user can create multiple movements with a name and movementType. A user can add workouts to a movement but since each movementType holds different data i create different structs for that.

ideally the array will always hold only one type of Movement based on the type of the movement.

The easiest method would be to declare the properties whose presence is uncertain as optional and create a common struct that combines both of the optional properties. Since we already have a MovementType that would provide certainty about the availability of a particular property we could probably force-unwrap, although safe-unwrap is safer at any point.

struct Movement: Identifiable, Codable {
    var id: UUID = UUID()
    let name: String
    let type: MovementType
    let workouts: [MovementDetail]
}

struct MovementDetail: Identifiable, Codable {
    var id: UUID = UUID()
    let weight: Double?
    let sets: Int
    let reps: Int
    let seconds: Int?
}

enum MovementType: String, Codable {
    case WeightMovement
    case RepsMovement
}

I would do it this way:

struct Movement: Identifiable, Codable {
    var id: UUID = UUID()
    var name: String
    var type: MovementType
    var weightWorkouts: [WeightMovement]
    var repsWorkouts: [RepsMovement]
    var workouts: [Codable] {
        switch type {
        case .WeightMovement:
            return weightWorkouts
        case .RepsMovement:
            return repsWorkouts
        }
    }
}

This doesn't do exactly what you describe, as there are multiple array types on the struct, but for the object's consumer, you have access to a single property workouts that will return one of multiple possible types.

As Tushar points out in his comment, though, it is fragile to specify what the return type is in two different places ( type and the actual type returned in the array). A subclass or protocol would probably be better.

ideally the array will always hold only one type of Movement based on the type of the movement.

In this case I recommend to decode the JSON manually and declare the type enum with associated types

enum MovementType {
    case weight([WeightMovement])
    case reps([RepsMovement])
}

struct Movement: Identifiable, Decodable {
    
    private enum CodingKeys : String, CodingKey { case name, type, workouts }
    
    let id: UUID = UUID()
    let name: String
    let type: MovementType
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        let movementType = try container.decode(String.self, forKey: .type)
        switch movementType {
            case "WeightMovement":
                let workouts = try container.decode([WeightMovement].self, forKey: .workouts)
                type = .weight(workouts)
            case "RepsMovement":
                let workouts = try container.decode([RepsMovement].self, forKey: .workouts)
                type = .reps(workouts)
            default: throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Invalid movement type")
        }
    }
}

struct WeightMovement: Identifiable, Decodable {
    let id: UUID = UUID()
    let weight: Double
    let sets: Int
    let reps: Int
}

struct RepsMovement: Identifiable, Decodable {
    let id: UUID = UUID()
    let sets: Int
    let reps: Int
    let seconds: Int
}

The explicit UUID assignment implies that id is not going to be decoded.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM