Я хотел бы объявить общий класс, который содержит / получает переменную типа Any?, но при запросе преобразует эту переменную в данный тип. Что-то вроде этого

class A<T> {
    var o: NSObject!
    var k: String!
    var v: T {
        get { return o.value(forKeyPath: k) as! T }
        set { o.setValue(newValue, forKeyPath: k) }
    }
}

Я хотел бы, чтобы это работало так, чтобы когда o.value(forKeyPath: k) равно nil и T мог удерживать nil (это ExpressibleByNilLiteral), v возвращает nil (типа T). Как бы то ни было, он вылетает из-за as!. Можно ли сделать это?

Попытка взломать это выглядит так (предложено Как сделать вычисляемое свойство универсального класса зависимым от ограничений класса)

class A<T> {
    var o: NSObject!
    var k: String!
    var v: T {
        get { 
            let x = o.value(forKeyPath: k)
            if let E = T.self as? ExpressibleByNilLiteral.Type {
                if let x = x { return x as! T }
                let n: T = <nil of type T> <---- not sure what to put here
                return n
            } else {
                return x as! T 
            }
        }
        set { o.setValue(newValue, forKeyPath: k) }
    }
}

Но я не уверен, как заставить его работать.

0
user3763801 5 Янв 2017 в 21:29
Не могли бы вы показать, как будет выглядеть действительный T (соответствующий ExpressibleByNilLiteral)?
 – 
Bogdan Farca
5 Янв 2017 в 21:37
В документации говорится: «nil имеет особое значение в Swift - отсутствие значения. ExpressibleByNilLiteral соответствует только типу Optional. Соответствие ExpressibleByNilLiteral типам, использующим nil для других целей, не рекомендуется».
 – 
Bogdan Farca
5 Янв 2017 в 21:38
Правильно, любой (и единственный) тип Optional будет соответствовать ExpressibleByNilLiteral. Для этого требуется только init(nilLiteral: ()), и мы можем написать let n: T = E.init(nilLiteral: ()) as! T (хотя комментарий в исходном коде для Optional, кажется, препятствует этому). К сожалению, это дает сбой, потому что E.init(nilLiteral: ()) ... nil и as! не работает. Это работает для любого протокола, который предоставляет какой-то init, который может быть вызван. Но реализация as! "запутана" с ExpressibleByNilLiteral, и вопрос в том, как их разделить.
 – 
user3763801
5 Янв 2017 в 22:31

1 ответ

Лучший ответ

Не знаю почему, но это действительно работает

func cast<T>(_ v: Any?)->T {
    if let E = T.self as? ExpressibleByNilLiteral.Type {
        if let v = v {
            return v as! T
        } else {
            return E.init(nilLiteral: ()) as! T
        }
    } else {
        return v as! T
    }
}

Я думал, что уже пробовал это, и это не сработало ... В любом случае, теперь мы можем заменить все вызовы as! на cast, когда мы не хотим сбой на nil, если тип мы кастинг можем взять, например в получателе моего вопроса.

1
user3763801 6 Янв 2017 в 20:40