iOS Swift4, как согласовать T.Type и type (of :) для передачи динамического типа класса в качестве параметра функции? - PullRequest
0 голосов
/ 01 октября 2018

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

Compiler Error: 
Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)

Я проверил документацию, и похоже, что метод type(of:) долженвернуть класс времени выполнения, в то время как компилятор заставляет его выглядеть, как будто он жалуется, потому что думает, что я передаю тип Any

Как передать имя класса Swift в качестве параметра функции без использования экземпляра этогоclass?

func retrieve<T>(type: T.Type) -> T? {

    let valueKey = String(describing: type)
    print("retrieving: \(valueKey)")
    return dictionary[valueKey] as? T
}
func update(with value: Any) {
    let valueKey = String(describing: type(of: value))
    print("Updating: \(valueKey)")
    dictionary[valueKey] = value
}

let testCases: [Any] = [1, 2.0, "text"]

for testCase in testCases {
    subject.update(with: testCase)
    //Compiler Error: Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
    let t = type(of: testCase)
    let retrieved = subject.retrieve(type: t)
    //check for equality
}
//this works
expect(subject.retrieve(type: Int.self)).to(equal(1))
expect(subject.retrieve(type: Double.self)).to(equal(2.0))
expect(subject.retrieve(type: String.self)).to(equal("text"))

Я провел дополнительное тестирование и вижу, что мой массив не соответствует документации type (of:), , и эта функция возвращаеттот же объект, что и «Любой»:

func retrieve<T>(sample: T) -> T? {

    let valueKey = String(describing: type(of: sample))
    print("retrieving: \(valueKey)") //always retrieves same object "Any"
    return dictionary[valueKey] as? T
}

Обновлено: Спасибо за ответы, чтобы прояснить - тестовые случаи предназначались для начала с простых типов, а затем переходили к более сложным классам.Фактическая реализация будет хранить совершенно уникальные экземпляры пользовательских типов, а не Strings или Ints.

let testCases: [Any] = [ConnectionConfig(...),
                                    AccountID("testID"),
                                    AccountName("testName")]

Тесты распознают универсальный характер функции получения и назначают соответствующие типы, о чем свидетельствует завершение кода:

    expect(subject.retrieve(type: ConnectionConfig.self)?.ip).to(equal("defaultIP"))
    expect(subject.retrieve(type: AccountID.self)?.value).to(equal("testId"))

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

class RxConfigConsumer: ConfigConsumer {
    var connection: ConnectionConfig?
    var accountID: AccountID?

    init(with provider: ConfigProvider) {
        connection = provider.retrieve(type: ConnectionConfig.self)
        accountID = provider.retrieve(type: AccountID.self)
        //etc
    }

}

Ответы [ 3 ]

0 голосов
/ 01 октября 2018

Короткий ответ - то, что вы пытаетесь сделать, невозможно, потому что нет способа ввести в примечание следующую строку кода:

let retrieved = subject.retrieve(type: t)

Что такое статический тип, известный при компиляциивремя retrieved?Это не может измениться во время выполнения.Это конечно не может измениться от итерации к итерации.Компилятор должен выделить для него место.Сколько места это требует?Требуется ли место в стеке или куче?Там нет никакого способа узнать.Лучшее, что мы можем сказать, это то, что это Any и поставить вокруг него рамку.1 даже не имеет правильного типа в любом случае.Это просто целочисленный литерал.Это может быть Float или много других вещей (попробуйте let x: Float = 1 и посмотрите).

Ответ: вы не можете построить такой цикл.Ваши индивидуальные тесты являются правильными.После создания [Any] очень трудно вернуть «настоящие» типы.Избегай это.Если у вас есть более конкретная проблема, помимо приведенного вами примера, мы можем обсудить, как с этим справиться, но я считаю, что вне модульного теста эта конкретная проблема не должна возникать в любом случае.

0 голосов
/ 01 октября 2018

Это интересный вопрос, и вы можете запустить следующие коды на игровой площадке.

Первым шагом является определение параметра T.Type.Трудно поместить это в вызов функции.Таким образом, для достижения вашей цели мы можем использовать T, но T.Type.

class MySubject {

 var dictionary : [String : Any] = [:]

 func retrieve<T>(type1: T) -> T? {

   let valueKey = String(describing: (type(of: type1)))
   print("retrieving: \(valueKey)")
   return dictionary[valueKey] as? T
  }
 func update(with value: Any) {
   let valueKey = String(describing: type(of: value))
    print("Updating: \(valueKey)")
    dictionary[valueKey] = value
   }

  }
 var subject : MySubject = MySubject()
 let testCases: [Any] = [1, 2.0, "text"]

for testCase in testCases {
    subject.update(with: testCase)
   //Compiler Error: Cannot invoke 'retrieve' with an argument list of type     '(type: Any.Type)

    let retrieved = subject.retrieve(type1: testCase)
    //check for equality
}

Компиляция правильная.Но, как вы сказали, возвращаемое значение равно нулю в результате универсальной функции получения.Чтобы достичь вашей цели, мы можем пропустить общий способ, использовать Any напрямую.

class MySubject {

var dictionary : [String : Any] = [:]

func retrieve(type1: Any) -> Any? {
    let valueKey = String(describing: (type(of: type1)))
    print("retrieving: \(valueKey)")
    return dictionary[valueKey]
}
func update(with value: Any) {
    let valueKey = String(describing: type(of: value))
    print("Updating: \(valueKey)")
    dictionary[valueKey] = value
}
}

var subject : MySubject = MySubject()
let testCases: [Any] = [1, 2.0, "text"]

for testCase in testCases {
  subject.update(with: testCase)
//Compiler Error: Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
  let retrieved = subject.retrieve(type1: testCase)
  //check for equality
 }

В настоящее время все идеально, как вы хотите.Но это вызывает интересную мысль о дженерике.Это нормально или правильно использовать универсальный здесь?Как мы знаем, в дженерике есть презумпция, которая представлена ​​буквами T, U, V. У них есть одно общее значение: Тип.Когда мы пытаемся использовать generic, мы предполагаем, что каждый параметр должен иметь только один уникальный тип.Таким образом, в первом случае Any является единственным типом и должен приниматься без вопросов в общем вызове.Во время вызова функции не будет обнаружено никакого другого типа.

Этот тип недопонимания коренится в использовании "let testCases: [Any] = [1, 2.0, "text"].Хотя swift позволяет писать таким образом, они не являются обычным массивом.Это список, который по сути содержит другой тип.Таким образом, вы можете игнорировать необычные общие здесь без каких-либо сожалений.Просто выберите Any, чтобы решить вашу проблему.

0 голосов
/ 01 октября 2018

Комбинация родового с метатипом (.Type) очень странная и, вероятно, то, что вас запутывает.Если вы избавляетесь от универсальных вещей, работаете так, как вы ожидаете:

func retrieve(_ T:Any.Type) {
    print(type(of:T))
}
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
    retrieve(type(of:testCase))
}
// Int.Type, Double.Type, String.Type

Если вы действительно хотите универсальное, то избавьтесь от .Type и напишите его так:

func retrieve<T>(_ t:T) {
    print(type(of:t))
}
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
    retrieve(type(of:testCase))
}
// Int.Type, Double.Type, String.Type

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

...