Подтвердить что ты не робот

Есть ли способ получить имя класса _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, велико.
  • Возможно, существует более одного универсального класса, но не много.
4b9b3361

Ответ 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
    }
}

Короче, создайте свои собственные идентификаторы классов и относительные функции, чтобы вернуть их. Вы сохраняете безопасность, поскольку, если класс не является известным классом, он не будет использоваться.