Как объявлять разные структуры внутри одного массива в Swift

#swift #struct

#swift #структура

Вопрос:

Возможно ли создать массив из разных структур? Моя структура данных выглядит следующим образом:

 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
}
 

Краткий пример того, что должно делать:
Пользователь может создавать несколько движений с именем и типом движения. Пользователь может добавлять тренировки к движению, но поскольку каждый movementType содержит разные данные, я создаю для этого разные структуры.

в идеале массив всегда будет содержать только один тип движения, основанный на type движении.

Комментарии:

1. Вы можете сделать так, чтобы ваши структуры модели подтверждались базовым типом, могли быть пустым классом или протоколом, основанным на ваших требованиях. Создайте массив базового типа и добавляйте объекты, используя композицию, вместо того, чтобы передавать каждый тип по отдельности, что само по себе неверно.

Ответ №1:

Самым простым методом было бы объявить свойства, присутствие которых неизвестно, как необязательные, и создать общее struct , объединяющее оба необязательных свойства. Поскольку у нас уже есть a MovementType , который обеспечивал бы уверенность в доступности определенного свойства, мы, вероятно, могли бы принудительно развернуть, хотя safe-unwrap безопаснее в любой момент.

 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
}
 

Ответ №2:

Я бы сделал это так:

 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
        }
    }
}
 

Это не совсем то, что вы описываете, поскольку в структуре существует несколько типов массивов, но для потребителя объекта у вас есть доступ к одному свойству workouts , которое возвращает один из нескольких возможных типов.

Однако, как указывает Тушар в своем комментарии, сложно указать, какой тип возвращаемого значения находится в двух разных местах ( type и фактический тип, возвращаемый в массиве). Подкласс или протокол, вероятно, были бы лучше.

Ответ №3:

в идеале массив всегда будет содержать только один тип движения в зависимости от типа движения.

В этом случае я рекомендую декодировать JSON вручную и объявить тип enum со связанными типами

 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
}
 

Явное присвоение UUID подразумевает, что id это не будет декодироваться.