Swift - возьмите ноль в качестве аргумента в общей функции с дополнительным аргументом
Я пытаюсь создать универсальную функцию, которая может принимать необязательный аргумент. Вот что у меня так далеко:
func somethingGeneric<T>(input: T?) {
if (input != nil) {
print(input!);
}
}
somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(nil) // Errors!
Работает с String
как показано, но не с nil
, Используя это с nil
дает следующие две ошибки:
error: cannot invoke 'somethingGeneric' with an argument list of type '(_?)'
note: expected an argument list of type '(T?)'
Что я делаю не так и как мне правильно объявить / использовать эту функцию? Кроме того, я хочу максимально упростить использование функции (я не хочу делать что-то вроде nil as String?
).
7 ответов
Я понял:
func somethingGeneric<T>(input: T?) {
if (input != nil) {
print(input!);
}
}
func somethingGeneric(input: NilLiteralConvertible?) {}
somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(nil) // *nothing printed*
somethingGeneric(nil as String?) // *nothing printed*
Я думаю, компилятор не может понять, что T
только из nil
,
Следующее работает просто отлично, хотя, например:
somethingGeneric(Optional<String>.None)
Я полагаю, что вы слишком усложнили проблему, потребовав способности передавать нетипизированный nil
(который на самом деле не существует; даже nil
имеет тип). Хотя подход в вашем ответе, кажется, работает, он позволяет создавать ??
типы из-за Факультативного продвижения. Вам часто везет, и это работает, но я видел, как это действительно расстраивает, и вызывается неправильная функция. Проблема в том, что String
может быть косвенно повышен до String?
а также String?
может быть косвенно повышен до String??
, когда ??
обнаруживается неявно, замешательство почти всегда следует.
Как указывает MartinR, ваш подход не очень интуитивен в отношении того, какая версия вызывается. UnsafePointer
это также NilLiteralConvertible
, Поэтому сложно рассуждать о том, какая функция будет вызываться. "Хитро рассуждать" делает его вероятным источником запутанных ошибок.
Единственный раз, когда ваша проблема существует, когда вы передаете буквальный nil
, Как отмечает @Valentin, если вы передадите переменную, nil
нет проблем; вам не нужен особый случай. Зачем заставлять звонящего проходить нетипизированный nil
? Просто пусть звонящий ничего не пропустит.
Я предполагаю что somethingGeneric
делает что-то действительно интересное в том случае, если это передается nil
, Если это не так; если код, который вы показываете, указывает на реальную функцию (т. е. все if (input != nil)
проверьте), то это не проблема. Просто не звони somethingGeneric(nil)
; это доказуемое бездействие. Просто удалите строку кода. Но я предполагаю, что есть какая-то "другая работа".
func somethingGeneric<T>(input: T?) {
somethingGeneric() // Call the base form
if (input != nil) {
print(input!);
}
}
func somethingGeneric() {
// Things you do either way
}
somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric() // Nothing
Хороший вопрос и ответ. У меня есть обновление Swift 4, чтобы внести свой вклад:
var str: String? = "Hello, playground"
var list: Array<String>? = ["Hello", "Coder256"]
func somethingGeneric<T>(_ input: T?) {
if (input != nil) {
print(input!);
}
}
func somethingGeneric(_ input: ExpressibleByNilLiteral?) {}
somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(nil) // *nothing printed*
somethingGeneric(nil as String?) // *nothing printed*
somethingGeneric(str) // Hello, playground
str = nil
somethingGeneric(str) // *nothing printed*
somethingGeneric(list) // ["Hello", "Coder256"]
list = nil
somethingGeneric(list) // *nothing printed*
На самом деле есть способ сделать это, вдохновленный внутренним кодом Alamofire.
Вам не нужно устанавливать Alamofire, чтобы использовать это решение.
Применение
Ваше проблемное определение метода
func someMethod<SomeGenericOptionalCodableType: Codable>(with someParam: SomeGenericOptionalCodableType? = nil) {
// your awesome code goes here
}
Что работает ✅
// invoke `someMethod` correctly
let someGoodParam1 = Alamofire.Empty.value
someMethod(with: someGoodParam1)
думаю можно использовать
Alamofire.Empty.value
как значение по умолчанию в
someMethod
определение как параметр.
Что не работает ❌
// invoke `someMethod` incorrectly
let someBadParam1: Codable? = nil
let someBadParam2 = nil
someMethod(with: someBadParam1)
someMethod(with: someBadParam2)
Определение решения (источник )
/// Type representing an empty value. Use `Empty.value` to get the static instance.
public struct Empty: Codable {
/// Static `Empty` instance used for all `Empty` responses.
public static let value = Empty()
}
Я думаю, что ты никогда не позвонишь somethingGeneric(nil)
Но в основном somethingGeneric(value)
или же somethingGeneric(function())
для которого у компилятора достаточно информации, чтобы не застрять, пытаясь угадать тип:
func somethingGeneric<T>(input: T?) {
if let input = input {
print(input);
}
}
func neverString() -> String? {
return nil
}
let a: String? = nil
somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(a) // Nothing and no error
somethingGeneric(neverString()) // Nothing and no error
Кроме того, я бы использовал if let
синтаксис вместо if(value != nil)
,
Вот решение, которое я придумал, которое компилируется на Swift 5, поскольку многие из решений здесь не компилировались для меня. Это может показаться хакерским, поскольку я использую хранимую переменную, чтобы помочь делу. Мне не удалось придумать версию Swift 5 с параметрами nil, которые разрешают вводитьT
.
class MyClass {
func somethingGeneric<T>(input: T?) {
if let input = input {
print(input)
}
}
func somethingGeneric() {
somethingGeneric(Object.Nil)
}
}
final class Object {
static var Nil: Object? //this should never be set
}