Есть ли способ получить имя класса _stable_ generic в Swift?
Получение имени класса в Swift довольно просто:
import Foundation
class Gen<T> {
init() { }
}
func printName(obj: AnyObject) {
dump(NSStringFromClass(obj.dynamicType))
}
let a: Gen<String> = Gen()
let b: Gen<Int> = Gen()
printName(a)
printName(b)
Однако, если вы запустите указанный выше код дважды, он не даст тот же результат.
Итак, существует ли способ реализовать printName
(получить в специализированном родовом имени класса) стабильным образом? Это несколько запусков кода печатает одну и ту же строку для одного и того же класса?
Я использую последнюю версию Xcode.
Некоторые дополнительные требования:
- Количество классов, которое будет передано
printName
, велико.
- Возможно, существует более одного универсального класса, но не много.
Ответы
Ответ 1
Новый запуск с Xcode 6.3 beta 1
Хорошие новости! Начиная с 6.3 вы можете:
toString(Gen<Int>())
И это напечатает: Gen<Swift.Int>
OLD Ответ:
Если область действия может быть уменьшена до T: AnyObject, вы можете реализовать ее следующим образом:
class Gen<T: AnyObject> {
var className: String {
return "Gen<\(NSStringFromClass(T.self))>"
}
}
Ответ 2
Извините, что разочаровал, но это кажется невозможным, если вы не активно пишете новый компилятор.
Проверьте этот источник. Автор описывает это довольно хорошо: Note that the isa pointer of the two objects is not identical, even though both are an instance of WrapperClass. Evidently, a specialization of a generic class is a separate class at runtime.
То, что я также играл, было @objc
, но это просто изменение пространства имен класса, но, очевидно, не влияет на имя класса.
Ответ 3
Это напечатает Gen
и будет работать для любого класса, насколько я могу судить:
class Gen<T> {
init() { }
}
func printName(obj: AnyObject) {
let desc = reflect(obj).summary
let scanner = NSScanner(string: desc)
var name: NSString?
scanner.scanUpToString(".", intoString: nil)
scanner.scanString(".", intoString: nil)
scanner.scanUpToString("", intoString: &name)
if let n = name {
println(n)
}
}
Но он не даст вам специализированного типа - Gen<String>
или Gen<Int>
, например - и я тестирую его прямо сейчас в онлайн-REPL, поэтому результаты могут варьироваться в рамках проекта или игровой площадки.
Вам нужно это, чтобы указать специализированный тип или достаточно имя класса?
EDITED: я объединил ответ @relejo и мой, чтобы дать вам специализированный тип, используя протокол и вычисленное свойство, которое он предложил:
protocol GenericClassName {
var className: String { get }
}
class Gen<T: AnyObject>: GenericClassName {
init() { }
var className: String {
return NSStringFromClass(T.self)
}
}
func printName<U: GenericClassName>(obj: U) {
let desc = reflect(obj).summary
let scanner = NSScanner(string: desc)
var name: NSString?
scanner.scanUpToString(".", intoString: nil)
scanner.scanString(".", intoString: nil)
scanner.scanUpToString("", intoString: &name)
if let n = name {
println(n + "<\(obj.className)>")
}
}
let gen1 = Gen<NSString>()
let gen2 = Gen<NSNumber>()
printName(gen1) // Outputs: Gen<NSString>
printName(gen2) // Outputs: Gen<NSNumber>
ИЗМЕНИТЬ СНОВА: С другой стороны, если вы собираетесь добавлять вычисленное свойство к каждому родовому классу (вы говорите, что у вас есть только 3-4 из них, верно?), просто сделайте так:
class Gen<T: AnyObject> {
init() { }
var className: String {
return (reflect(self).summary.componentsSeparatedByString(".").last ?? "") + "<\(NSStringFromClass(T.self))>"
}
}
Теперь вы можете получить имя класса как строку:
let gen1 = Gen<NSString>()
println(gen1.className) // Output: Gen<NSString>
Таким образом, вы не возитесь с протоколами или глобальными функциями или NSScanner
(что может быть медленным...).
Ответ 4
Построение верхнего ответа, если вы просто хотите имя фактического класса, не включая "модуль", в котором он находится, просто разделите ".". и получить последний элемент этого массива. Мне нужно было получить общее имя, чтобы написать общую функцию данных ядра, чтобы иметь возможность вызывать NSManagedObject.insertObjectIntoContext, поэтому я не вызываю NSEntityDescription.insertNewObject... повсюду.
код:
func insertNewObjectIntoContext<E>(moc: NSManagedObjectContext) -> E {
return NSEntityDescription.insertNewObjectForEntityForName(toString(E).componentsSeparatedByString(".").last!, inManagedObjectContext: moc) as! E
}
Часть интереса к этому вопросу:
toString(E).componentsSeparatedByString(".").last! // returns E - rather than ModuleName.E
Ответ 5
Возможно, это может быть намек на решение, которое вы ищете:
class Gen<T> {
init() { }
}
enum ClassID: String {
case GenInt = "Gen<Int>"
case GenString = "Gen<String>"
func classFromID(stringID: String) -> AnyClass? {
if let classID = ClassID(rawValue: stringID) {
switch classID {
case .GenInt:
return Gen<Int>().dynamicType
case .GenString:
return Gen<String>().dynamicType
}
}
return nil
}
}
Короче, создайте свои собственные идентификаторы классов и относительные функции, чтобы вернуть их. Вы сохраняете безопасность, поскольку, если класс не является известным классом, он не будет использоваться.