Типизация в Swift

Я пишу библиотеку, которая может анализировать типизированные идентификаторы из JSON. Тем не менее, я считаю, что правила приведения типов немного озадачивают.

Пример:

class AccountId : NSString { }

let json : AnyObject? = "user-1" // Returned by NSJSONSerialization.JSONObjectWithData
let s = json as? NSString   // Succeeds, s == Some("user-1")
let a = json as? AccountId  // Fails, a == nil

Почему первый тип будет успешным, а второй - неудачным? Есть ли что-то волшебное в NSString, которое не пересекает классы Swift?

Я использую XCode Version 6.1 (6A1030) (последний на момент написания).

Ответ 1

Как правило, если у вас есть иерархия классов A → B → C (C наследует от B, которая, в свою очередь, наследуется от A), и у вас есть экземпляр B, вы можете повышать до A, но вы не можете понизить до C.

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

Обратите внимание, что полиморфизм позволяет вам делать upcast и downcast с переменными - это означает, что если у вас есть экземпляр C, хранящийся в переменной типа A, вы можете применить эту переменную к B и C, потому что на самом деле она содержит экземпляр of C. Но если переменная содержит экземпляр B, вы можете понизить до B, но не до C.

В вашем случае, а не в downcasting, вы должны специализировать конструктор, принимающий NSString, но я подозреваю, что в этом конкретном случае NSString он не может быть выполнен (там нет NSString назначенный инициализатор, принимающий строку в качестве аргумента), Если вы в состоянии это сделать, тогда ваш код будет выглядеть так:

var json: AnyObject? = "test"
if let string = json as? NSString {
    let a = AccountId(string: string)
}

и в этот момент вы можете использовать a, где ожидается экземпляр либо AccountId, либо NSString

Ответ 2

Есть ли что-то волшебное в NSString, которое не пересекается с классами Swift?

Да. Класс Swift String автоматически соединяется с NSString и наоборот. Из документы:

Swift автоматически соединяет между строковым типом и NSString класс. Это означает, что везде, где вы используете объект NSString, вы можете используйте вместо этого тип Swift String и получите преимущества обоих типы - интерполяция типов строк и Swift-разработанные API и Широкие функциональные возможности классов NSString. По этой причине вы должны почти никогда не нужно использовать класс NSString непосредственно в вашем собственном коде. Фактически, когда Swift импортирует API Objective-C, он заменяет все Типы NSString со строковыми типами. Когда ваш код Objective-C использует Swift, импортер заменяет все типы строк на NSString в импортированном API.

Почему первый тип будет успешным, а второй - неудачным?

Из-за сильной связи между String и NSString компилятор знает, как конвертировать из одного в другой. С другой стороны, поскольку ваш AccountId класс является более специализированной версией NSString, вы не можете отбрасывать от String до AccountId.