Як надрукувати тип або клас змінної у Swift?


335

Чи є спосіб швидко друкувати тип виконання змінної? Наприклад:

var now = NSDate()
var soon = now.dateByAddingTimeInterval(5.0)

println("\(now.dynamicType)") 
// Prints "(Metatype)"

println("\(now.dynamicType.description()")
// Prints "__NSDate" since objective-c Class objects have a "description" selector

println("\(soon.dynamicType.description()")
// Compile-time error since ImplicitlyUnwrappedOptional<NSDate> has no "description" method

У наведеному вище прикладі я шукаю спосіб показати, що змінна "скоро" має тип ImplicitlyUnwrappedOptional<NSDate>, або принаймні NSDate!.


43
@JasonMArcher Скажіть мені, як це дублікат, якщо відповідне вами питання було задано через 4 дні після цього?
акашівський

Існує ряд питань щодо тестування типу об'єкта Swift або зчитування типу об'єкта Swift. Ми просто знаходимо найкращі питання, які можна використовувати як «майстерні» запитання для цієї теми. Пропонований дублікат має набагато більш ретельну відповідь. Це не означає, що ви щось зробили не так, просто ми намагаємось зменшити безлад.
JasonMArcher

4
Пропонований дублікат не відповідає на те саме запитання; Type.self не може бути надрукований на консоль з метою налагодження, він повинен використовуватися для передачі іншим функціям, які приймають типи як об'єкти.
Метт Бріджес

2
ОТ: Дуже дивно, що Свіфт не пропонує цього поза коробкою, і потрібно пограбувати з такими низькорівневими бібліотеками С. Варто повідомлення про помилку?
qwerty_so

Хлопці, я надав свою відповідь нижче. Будь ласка, погляньте і дайте мені знати, чи це очікується.
ДІЛІП КОСУРІ

Відповіді:


377

Оновлення вересня 2016 року

Swift 3.0: Використовуйте type(of:), наприклад type(of: someThing)(оскільки dynamicTypeключове слово було видалено)

Оновлення жовтня 2015 року :

Я оновив наведені нижче приклади до нового синтаксису Swift 2.0 (наприклад, printlnзамінено на print, toString()зараз String()).

З приміток до випуску Xcode 6.3 :

@nschum в коментарях вказує, що примітки до випуску Xcode 6.3 показують інший спосіб:

Значення типу тепер друкуються як повне ім'я типу "демонтаж" при використанні з println або рядковою інтерполяцією.

import Foundation

class PureSwiftClass { }

var myvar0 = NSString() // Objective-C class
var myvar1 = PureSwiftClass()
var myvar2 = 42
var myvar3 = "Hans"

print( "String(myvar0.dynamicType) -> \(myvar0.dynamicType)")
print( "String(myvar1.dynamicType) -> \(myvar1.dynamicType)")
print( "String(myvar2.dynamicType) -> \(myvar2.dynamicType)")
print( "String(myvar3.dynamicType) -> \(myvar3.dynamicType)")

print( "String(Int.self)           -> \(Int.self)")
print( "String((Int?).self         -> \((Int?).self)")
print( "String(NSString.self)      -> \(NSString.self)")
print( "String(Array<String>.self) -> \(Array<String>.self)")

Які виходи:

String(myvar0.dynamicType) -> __NSCFConstantString
String(myvar1.dynamicType) -> PureSwiftClass
String(myvar2.dynamicType) -> Int
String(myvar3.dynamicType) -> String
String(Int.self)           -> Int
String((Int?).self         -> Optional<Int>
String(NSString.self)      -> NSString
String(Array<String>.self) -> Array<String>

Оновлення для Xcode 6.3:

Ви можете використовувати _stdlib_getDemangledTypeName():

print( "TypeName0 = \(_stdlib_getDemangledTypeName(myvar0))")
print( "TypeName1 = \(_stdlib_getDemangledTypeName(myvar1))")
print( "TypeName2 = \(_stdlib_getDemangledTypeName(myvar2))")
print( "TypeName3 = \(_stdlib_getDemangledTypeName(myvar3))")

і отримайте це як вихід:

TypeName0 = NSString
TypeName1 = __lldb_expr_26.PureSwiftClass
TypeName2 = Swift.Int
TypeName3 = Swift.String

Оригінальна відповідь:

До Xcode 6.3 _stdlib_getTypeNameотримав назву змінної типу змінної. Запис у блозі Юана Свіка допомагає розшифрувати ці рядки:

наприклад, _TtSiозначає внутрішній Intтип Свіфта .

Майк Еш має чудовий запис у блозі, що висвітлює ту саму тему .


2
Як ти знайшов _stdlib_getTypeName()? Інтегрованої REPL більше не існує, swift-ide-testтакож не існує, і друк Xcode модуля Swift відміняє _попередньо встановлені значення.
Лілі Баллард

10
Схоже, я можу шукати символи lldbдосить легко у відповіді. Є також _stdlib_getDemangledTypeName()і _stdlib_demangleName().
Лілі Баллард

1
О, акуратно, я не розумів, що imageце ярлик target modules. Я закінчив використовувати target modules lookup -r -n _stdlib_, що, звичайно, краще виражається якimage lookup -r -n _stdlib_ libswiftCore.dylib
Лілі Баллард

1
FYI - Схоже, toString (...) було замінено на String (...).
Нік

6
Swift 3.0: dynamicTypeключове слово було видалено з Swift. На його місці новий примітив функція type(of:)була додана до мови: github.com/apple/swift/blob/master/CHANGELOG.md
СЗУ

46

Редагувати: Нова toStringфункція була введена в Swift 1.2 (Xcode 6.3) .

Тепер ви можете надрукувати розібраний тип будь-якого типу за допомогою .selfта будь-якого примірника, використовуючи .dynamicType:

struct Box<T> {}

toString("foo".dynamicType)            // Swift.String
toString([1, 23, 456].dynamicType)     // Swift.Array<Swift.Int>
toString((7 as NSNumber).dynamicType)  // __NSCFNumber

toString((Bool?).self)                 // Swift.Optional<Swift.Bool>
toString(Box<SinkOf<Character>>.self)  // __lldb_expr_1.Box<Swift.SinkOf<Swift.Character>>
toString(NSStream.self)                // NSStream

Спробуйте зателефонувати YourClass.selfта yourObject.dynamicType.

Довідка: https://devforums.apple.com/thread/227425 .


2
Я відповів на цю тему, але я додам тут свій коментар. Ці значення мають тип "Метатип", але, здається, не є простим способом з'ясувати, який тип об'єкту метатипу представляє, що саме я шукаю.
Метт Бріджес

Вони типу метатипу, і вони є класами. Вони представляють клас. Не впевнений, в чому саме полягає ваша проблема. someInstance.dynamicType.printClassName()буде надрукувати назву класу.
Аналоговий файл

18
метод printClassName () - це лише приклад методу, який вони створили в одному з зразків в документах, це не виклик API, до якого ви можете отримати доступ.
Дейв Вуд

4
FYI - Схоже, toString (...) замінено на String (...)
Нік

33

Swift 3.0

let string = "Hello"
let stringArray = ["one", "two"]
let dictionary = ["key": 2]

print(type(of: string)) // "String"

// Get type name as a string
String(describing: type(of: string)) // "String"
String(describing: type(of: stringArray)) // "Array<String>"
String(describing: type(of: dictionary)) // "Dictionary<String, Int>"

// Get full type as a string
String(reflecting: type(of: string)) // "Swift.String"
String(reflecting: type(of: stringArray)) // "Swift.Array<Swift.String>"
String(reflecting: type(of: dictionary)) // "Swift.Dictionary<Swift.String, Swift.Int>"

1
Реалізація Swift 2 особливо приємна у випадках із перерахунками. Хтось знає, чи таке поведінка документується / гарантується Apple? Стандартна бібліотека містить лише коментар про те, що це підходить для налагодження ...
milos

30

Це те, що ви шукаєте?

println("\(object_getClassName(now))");

Він друкує "__NSDate"

ОНОВЛЕННЯ : Зверніть увагу, що, здається, це більше не працює з Beta05


2
Не працює для швидких занять. Повертається "UnsafePointer (0x7FBB18D06DE0)"
aryaxt

4
Не працює і для класів ObjC (принаймні, у Beta5), для NSManagedObject я просто отримую "Builtin.RawPointer = адреса"
Kendall Helmstetter Gelner

23

Мій поточний Xcode - версія 6.0 (6A280e).

import Foundation

class Person { var name: String; init(name: String) { self.name = name }}
class Patient: Person {}
class Doctor: Person {}

var variables:[Any] = [
    5,
    7.5,
    true,
    "maple",
    Person(name:"Sarah"),
    Patient(name:"Pat"),
    Doctor(name:"Sandy")
]

for variable in variables {
    let typeLongName = _stdlib_getDemangledTypeName(variable)
    let tokens = split(typeLongName, { $0 == "." })
    if let typeName = tokens.last {
        println("Variable \(variable) is of Type \(typeName).")
    }
}

Вихід:

Variable 5 is of Type Int.
Variable 7.5 is of Type Double.
Variable true is of Type Bool.
Variable maple is of Type String.
Variable Swift001.Person is of Type Person.
Variable Swift001.Patient is of Type Patient.
Variable Swift001.Doctor is of Type Doctor.

1
Добре, але питання також включало необов'язкові параметри, виводиться додаткові параметри Optional(...), тому ваш код повертається лише Optionalяк Тип. Він також повинен відображатися var optionalTestVar: Person? = Person(name:"Sarah")як необов’язковий (Person) та var з Person! як
ImplicitlyUnwrappedOptions

18

Станом на Xcode 6.3 з Swift 1.2, ви можете просто перетворити значення типів у повний демонтаж String.

toString(Int)                   // "Swift.Int"
toString(Int.Type)              // "Swift.Int.Type"
toString((10).dynamicType)      // "Swift.Int"
println(Bool.self)              // "Swift.Bool"
println([UTF8].self)            // "Swift.Array<Swift.UTF8>"
println((Int, String).self)     // "(Swift.Int, Swift.String)"
println((String?()).dynamicType)// "Swift.Optional<Swift.String>"
println(NSDate)                 // "NSDate"
println(NSDate.Type)            // "NSDate.Type"
println(WKWebView)              // "WKWebView"
toString(MyClass)               // "[Module Name].MyClass"
toString(MyClass().dynamicType) // "[Module Name].MyClass"

Чи можливий інший напрямок у 1,2? Ініціалізувати екземпляр на основі назви класу?
user965972

@ user965972 Наскільки я знаю, я не думаю, що вони є.
дітям

17

Ви все одно можете отримати доступ до класу через className(який повертає a String).

Є на насправді кілька способів , щоб отримати клас, наприклад classForArchiver, classForCoder, classForKeyedArchiver(всі поворотні AnyClass!).

Ви не можете отримати тип примітиву (примітив - це не клас).

Приклад:

var ivar = [:]
ivar.className // __NSDictionaryI

var i = 1
i.className // error: 'Int' does not have a member named 'className'

Якщо ви хочете отримати тип примітиву, вам доведеться скористатися bridgeToObjectiveC(). Приклад:

var i = 1
i.bridgeToObjectiveC().className // __NSCFNumber

Б'юсь об заклад, що це примітив. На жаль, ти не можеш отримати тип примітиву.
Леандрос

Ці приклади, здається, не працюють на дитячому майданчику. Я отримую помилку і для примітивів, і для примітивів.
Метт Бріджес

спробувати import Cocoa.
Леандрос

9
схоже, що className доступний лише для NSObjects при розробці для OSX; він не доступний в SDK для iOS ні для NSObjects, ні для "нормальних" об'єктів
Matt Bridges

3
classNameє частиною підтримки Apple® AppleScript і, безумовно, не повинна використовуватися для загальної самоаналізу. Очевидно, що він не доступний на iOS.
Микола Рухе

16

Ви можете використовувати відображення для отримання інформації про об'єкт.
Наприклад ім'я класу об'єктів:

var classname = відобразити (зараз)

У бета-версії 6 це отримало мені назву програми + ім'я класу в трохи збитому вигляді, але це початок. +1 за reflect(x).summary- дякую!
Олі

не вийшло, друкує порожній рядок у Swift 1.2 Xcode 6.4 та iOs 8.4
Juan Boero

13

Мені пощастило:

let className = NSStringFromClass(obj.dynamicType)

@hdost чувак нічого не працював, але класForForCoder працював.
Satheeshwaran


10

У Xcode 8 Swift 3.0

Кроки:

1. Отримайте тип:

Варіант 1:

let type : Type = MyClass.self  //Determines Type from Class

Варіант 2:

let type : Type = type(of:self) //Determines Type from self

2. Перетворити тип у рядок:

let string : String = "\(type)" //String


7

SWIFT 5

З останньою версією Swift 3 ми можемо отримати досить описи імен типів через Stringініціалізатор. Як, наприклад print(String(describing: type(of: object))). Де object може бути змінна примірник, наприклад, масив, словник, an Int, a NSDate, екземпляр спеціального класу тощо.

Ось моя повна відповідь: Отримайте назву класу об'єкта як рядок у Swift

Це питання шукає спосіб отримати назву класу об'єкта як рядка, але я також запропонував інший спосіб отримати ім'я класу змінної, яка не є підкласом NSObject. Ось:

class Utility{
    class func classNameAsString(obj: Any) -> String {
        //prints more readable results for dictionaries, arrays, Int, etc
        return String(describing: type(of: obj))
    }
}

Я зробив статичну функцію, яка приймає за параметр об'єкт типу Anyі повертає його ім'я класу як String:).

Я перевірив цю функцію за допомогою деяких змінних, таких як:

    let diccionary: [String: CGFloat] = [:]
    let array: [Int] = []
    let numInt = 9
    let numFloat: CGFloat = 3.0
    let numDouble: Double = 1.0
    let classOne = ClassOne()
    let classTwo: ClassTwo? = ClassTwo()
    let now = NSDate()
    let lbl = UILabel()

а вихід:

  • дикціонер є словником типу
  • масив типу Array
  • numInt типу Int
  • numFloat має тип CGFloat
  • numDouble має тип Double
  • classOne має тип: ClassOne
  • classTwo - тип: ClassTwo
  • зараз тип: дата
  • lbl типу: UILabel

5

Використовуючи Cocoa (не CocoaTouch), ви можете використовувати classNameвластивість для об'єктів, які є підкласами NSObject.

println(now.className)

Це властивість недоступне для звичайних об'єктів Swift, які не є підкласами NSObject (а насправді у Swift немає кореневого ідентифікатора чи типу об'єкта).

class Person {
    var name: String?
}

var p = Person()
println(person.className) // <- Compiler error

У CocoaTouch на даний момент немає способу отримати рядковий опис типу заданої змінної. Подібна функціональність також не існує для примітивних типів ні в какао, ні в какао.

Swift REPL може роздрукувати підсумок значень, включаючи його тип, тому можливо, такий спосіб самоаналізу буде можливий через API в майбутньому.

РЕДАКТУВАННЯ : dump(object)схоже, це робиться.


Навіть на Xcode 10 та Swift 4.2, dump(object)це справді потужно. Дякую.
Олексій Заватоне

5

У верхній відповіді немає робочого прикладу нового способу зробити це за допомогою type(of:. Отож, щоб допомогти новачкам, як я, ось робочий приклад, взятий здебільшого з документів Apple тут - https://developer.apple.com/documentation/swift/2885064-type

doubleNum = 30.1

func printInfo(_ value: Any) {
    let varType = type(of: value)
    print("'\(value)' of type '\(varType)'")
}

printInfo(doubleNum)
//'30.1' of type 'Double'

4

Я спробував деякі інші відповіді тут, але пробіг, здається, дуже залежить від того, що є основним об'єктом.

Однак я знайшов спосіб отримати ім'я класу Object-C для об'єкта, зробивши наступне:

now?.superclass as AnyObject! //replace now with the object you are trying to get the class name for

Ось і приклад того, як ви його використали:

let now = NSDate()
println("what is this = \(now?.superclass as AnyObject!)")

У цьому випадку він буде друкувати NSDate в консолі.


4

Я знайшов таке рішення, яке, сподіваємось, може працювати для когось іншого. Я створив метод класу для доступу до значення. Зверніть увагу, що це працюватиме лише для підкласу NSObject. Але принаймні - чисте і охайне рішення.

class var className: String!{
    let classString : String = NSStringFromClass(self.classForCoder())
    return classString.componentsSeparatedByString(".").last;
}

4

В останньому XCode 6.3 з Swift 1.2 це єдиний спосіб, який я знайшов:

if view.classForCoder.description() == "UISegment" {
    ...
}

ПРИМІТКА: description () повертає ім'я класу у форматі <swift module name>.<actual class name>, тому ви хочете розділити рядок на a .і отримати останній маркер.
Меттью Кірос

4

Багато відповідей тут не працюють із останнім Swift (Xcode 7.1.1 під час написання).

Сучасний спосіб отримання інформації полягає у створенні Mirrorта допиті цього. Для назви класу це так просто, як:

let mirror = Mirror(reflecting: instanceToInspect)
let classname:String = mirror.description

Додаткову інформацію про об'єкт можна також отримати в Mirror. Докладні відомості див. У розділі http://swiftdoc.org/v2.1/type/Mirror/ .


Що не так у відповідях stackoverflow.com/a/25345480/1187415 та stackoverflow.com/a/32087449/1187415 ? Обидва, здається, добре працюють із Swift 2. (Не перевірили інших.)
Martin R

Мені це подобається як загальне рішення, але отримання "Дзеркала для ViewController" (стороннє "Дзеркало для") - зачекайте, схоже, використання ".subjectType" робить трюк!
Девід Н

3

У lldb бета-версії 5 ви можете побачити клас об'єкта за допомогою команди:

fr v -d r shipDate

який виводить щось на кшталт:

(DBSalesOrderShipDate_DBSalesOrderShipDate_ *) shipDate = 0x7f859940

Команда розгорнута означає щось на кшталт:

Frame Variable(надрукувати змінну кадру) -d run_target(розширити динамічні типи)

Щось корисне знати, це те, що використання "змінної кадру" для виведення змінних значень гарантує, що код не виконується.


3

Я знайшов рішення для самостійно розроблених класів (або таких, до яких у вас є доступ).

Розмістіть таке обчислене властивість у визначенні класу об'єктів:

var className: String? {
    return __FILE__.lastPathComponent.stringByDeletingPathExtension
}

Тепер ви можете просто назвати ім'я класу на вашому об'єкті так:

myObject.className

Зверніть увагу, що це буде працювати лише в тому випадку, якщо визначення вашого класу буде зроблено у файлі, названому точно так, як клас, для якого ви хочете назвати.

Оскільки це зазвичай так, наведена вище відповідь повинна робити це в більшості випадків . Але в деяких особливих випадках вам може знадобитися розглянути інше рішення.


Якщо вам потрібна назва класу в самому класі (файлі), ви можете просто скористатися цим рядком:

let className = __FILE__.lastPathComponent.stringByDeletingPathExtension

Можливо, цей метод допомагає деяким людям там.


3

На основі відповідей та коментарів Класса та Кевіна Балларда, я б пішов із:

println(_stdlib_getDemangledTypeName(now).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(soon).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(soon?).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(soon!).componentsSeparatedByString(".").last!)

println(_stdlib_getDemangledTypeName(myvar0).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(myvar1).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(myvar2).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(myvar3).componentsSeparatedByString(".").last!)

який буде надруковано:

"NSDate"
"ImplicitlyUnwrappedOptional"
"Optional"
"NSDate"

"NSString"
"PureSwiftClass"
"Int"
"Double"

Я думаю, що це краща відповідь.
maschall

Я в кінцевому підсумку використав у Swift 1.2 наступне: toString(self.dynamicType).componentsSeparatedByString(".").last!явно опинився в об'єктному контексті і міг посилатися self.
Джо

3
let i: Int = 20


  func getTypeName(v: Any) -> String {
    let fullName = _stdlib_demangleName(_stdlib_getTypeName(i))
    if let range = fullName.rangeOfString(".") {
        return fullName.substringFromIndex(range.endIndex)
    }
    return fullName
}

println("Var type is \(getTypeName(i)) = \(i)")

3

Швидка версія 4:

print("\(type(of: self)) ,\(#function)")
// within a function of a class

Дякуємо @Joshua Dance


3

Швидкий 4:

// "TypeName"
func stringType(of some: Any) -> String {
    let string = (some is Any.Type) ? String(describing: some) : String(describing: type(of: some))
    return string
}

// "ModuleName.TypeName"
func fullStringType(of some: Any) -> String {
    let string = (some is Any.Type) ? String(reflecting: some) : String(reflecting: type(of: some))
    return string
}

Використання:

print(stringType(of: SomeClass()))     // "SomeClass"
print(stringType(of: SomeClass.self))  // "SomeClass"
print(stringType(of: String()))        // "String"
print(fullStringType(of: String()))    // "Swift.String"

2

Здається, немає загального способу друкувати ім'я типу типу довільного значення. Як зазначали інші, для екземплярів класу ви можете друкувати, value.classNameале для примітивних значень виявляється, що під час виконання інформація про тип не втрачається.

Наприклад, схоже, що немає способу набрати: 1.something()і вийти з Intбудь-якого значення something. (Ви можете, як запропонована інша відповідь, використовувати, i.bridgeToObjectiveC().classNameщоб дати вам підказку, але насправді __NSCFNumberце не тип i- лише те, до чого він буде перетворений, коли він перетне межу виклику функції Objective-C.)

Я був би радий, що він виявився неправильним, але, схоже, перевірка типу виконується під час компіляції, і, як, наприклад, C ++ (з відключеною RTTI), значна частина інформації про тип проходить під час виконання.


Чи можете ви навести приклад використання className для друку "NSDate" для змінної "зараз" у моєму прикладі, що працює на ігровому майданчику Xcode6? Наскільки я можу сказати, className не визначений на об'єктах Swift, навіть тих, які є підкласами NSObject.
Метт Бріджз

Здається, що "className" доступний для об'єктів, які сходять з NSObject, але лише при розробці для mac, а не для iOS. Здається, що для iOS взагалі немає вирішення.
Метт Бріджз

1
Так, справжній винос тут, здається, полягає в тому, що в загальному випадку інформація про тип швидко проходить під час виконання.
ipmcc

Мені цікаво, як REPL здатний друкувати типи значень.
Метт Бріджз

REPL майже напевно інтерпретується та не складається, що легко пояснить це
ipmcc

2

Ось так ви отримуєте рядковий тип вашого об'єкта або типу, який є послідовним і враховує, до якого модуля належить або вкладено визначення об'єкта. Працює в Swift 4.x.

@inline(__always) func typeString(for _type: Any.Type) -> String {
    return String(reflecting: type(of: _type))
}

@inline(__always) func typeString(for object: Any) -> String {
    return String(reflecting: type(of: type(of: object)))
}

struct Lol {
    struct Kek {}
}

// if you run this in playground the results will be something like
typeString(for: Lol.self)    //    __lldb_expr_74.Lol.Type
typeString(for: Lol())       //    __lldb_expr_74.Lol.Type
typeString(for: Lol.Kek.self)//    __lldb_expr_74.Lol.Kek.Type
typeString(for: Lol.Kek())   //    __lldb_expr_74.Lol.Kek.Type

2

Щоб отримати тип об'єкта чи класу об'єкта в Swift , вам потрібно використовувати тип (of: yourObject)

тип (of: yourObject)


1

Не зовсім те, що ви шукаєте, але ви також можете перевірити тип змінної щодо типів Swift, як-от так:

let object: AnyObject = 1

if object is Int {
}
else if object is String {
}

Наприклад.


1

Xcode 7.3.1, Swift 2.2:

String(instanceToPrint.self).componentsSeparatedByString(".").last

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.