Общий ввод функции Swift соответствует как классу, так и протоколу

#swift #generics #delegates #protocols

#Swift #Общие #Делегаты #Протоколы

Вопрос:

Я пытаюсь заставить функцию принимать общий ввод, соответствующий как классу, так и протоколу, чтобы обрабатывать значения, в которых некоторые принадлежат классу, а другие принадлежат делегату. Он отлично работает для отдельных типов, но не для массива типов (которые соответствуют как суперклассу, так и делегату). Вместо этого компилятор выдает ошибку «Метод экземпляра ‘printValues(for:)’ требует, чтобы ‘MeasurementClass’ соответствовал ‘UnitDelegate'»

Я добавил приведенный ниже код, который будет иметь гораздо больше смысла. Просто поместите его на игровую площадку Swift. Если вы запустите его как есть, вы увидите, что он правильно выводит расстояние и шаги пользователя по отдельности. Если вы раскомментируете последние несколько строк, ту же функцию нельзя будет использовать с массивом.

Моя цель — иметь возможность передавать массив типов, которые соответствуют UnitDelegate и MeasurementClass (он же MeasurementObject typealias) и обрабатывать значения. Мне нужен массив, потому что мне понадобится иметь кучу разных классов, которые соответствуют MeasurementObject.

 protocol UnitDelegate: class{
    var units: [String: String] { get }
}

class MeasurementClass{
    var imperial: Double!
    var metric: Double!
    
    convenience init(imperial: Double, metric: Double){
        self.init()
        self.imperial = imperial
        self.metric = metric
    }
}

typealias MeasurementObject = MeasurementClass amp; UnitDelegate


class Distance: MeasurementObject{
    var units = ["imperial": "miles", "metric":"kms"]
}

class Steps: MeasurementObject{
    var units = ["imperial": "steps", "metric":"steps"]
}

class User{
    func printValues<T: MeasurementObject>(for type: T) {
        print("(type.imperial!) (type.units["imperial"]!) = (type.metric!) (type.units["metric"]!)")
    }
}

//This is what I'm trying to achieve in the for loop below
let distance = Distance(imperial: 30, metric: 48.28)
let steps    = Steps(imperial: 30, metric: 30)
let user     = User()
user.printValues(for: distance)
user.printValues(for: steps)


//let types = [distance, steps]
//
//for type in types{
//    user.printValues(for: type)
//}
 

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

1. Это не так, как работает generic. Вы не можете смешивать общие типы в коллекции. Универсальный тип может быть любого типа, но только по одному за раз

2. ` var imperial: удваивается! Метрика var: двойная! ` Я гарантирую вам, что эти два значения не будут синхронизированы. Сохраните только один источник достоверности, например, стандартное значение метрики, и сделайте имперское измерение вычисляемым свойством, которое получает правильное преобразование на лету. Таким образом, он никогда не может выйти из синхронизации

3. @Alexander-RestorateMonica Я подумал об этом и решил, что это лучшее. Я храню кучу типов работоспособности на сервере, и, сохраняя imperial и metric, я могу просто извлечь правильное значение. Если бы у меня был только imperial, мне пришлось бы каким-то образом переключить тип данных, которые извлекаются для выполнения преобразования. Имея все записи работоспособности, хранящиеся с одинаковыми значениями на сервере, на стороне собственного приложения я могу использовать один класс для декодирования каждого типа.

4. @RichardWitherspoon я предлагаю вам использовать одно значение как единственное, которое стоит сохранить. Взгляните на API единиц измерения и измерений , встроенный Apple в Foundation. Из того немногого, что я видел о вашем коде / проблеме, это выглядит как точное соответствие тому, что вы пытаетесь сделать. Если нет, вы могли бы хотя бы сослаться на него, например, на их базовые единицы: developer.apple.com/documentation/foundation/dimension

5. Я понятия не имел об этом, это будет идеально! Большое вам спасибо.

Ответ №1:

Это не прямой ответ на ваш вопрос, но все, что вам нужно, это добавить перечисление для хранения типа измерения в вашем классе и вычисляемое свойство для возврата соответствующего словаря. Нет необходимости использовать протокол, состав протокола, класс и / или подкласс для выполнения того, что вы пытаетесь сделать:

 struct AMeasurement {
    let imperial: Double
    let metric: Double
    let kind: Kind
    enum Kind { case distance, steps }
    var units: [String: String] {
        switch kind {
        case .distance: return ["imperial": "miles", "metric":"kms"]
        case .steps: return ["imperial": "steps", "metric":"steps"]
        }
    }
}
 

 extension AMeasurement {
    func printValues() {
        print("(imperial) (units["imperial"]!) = (metric) (units["metric"]!)")
    }
}
 

 let distance = AMeasurement(imperial: 30, metric: 48.28, kind: .distance)
let steps = AMeasurement(imperial: 30, metric: 30, kind: .steps)
let types = [distance, steps]

for type in types {
    type.printValues()
}
 

30,0 миль = 48,28 км
30,0 шагов = 30,0 шагов

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

1. Это гораздо более простой подход к решению моей проблемы. Кажется, я слишком усложнил ответ. Спасибо за помощь!