Lấy tên lớp của đối tượng dưới dạng chuỗi trong Swift


320

Lấy tên lớp của một đối tượng khi Stringsử dụng:

object_getClassName(myViewController)

trả về một cái gì đó như thế này:

_TtC5AppName22CalendarViewController

Tôi đang tìm kiếm phiên bản thuần túy : "CalendarViewController". Làm thế nào để tôi có được một chuỗi sạch của tên lớp thay thế?

Tôi tìm thấy một số nỗ lực của câu hỏi về điều này nhưng không phải là một câu trả lời thực sự. Có phải là không thể nào cả?


Ngoài ra, một chức năng phân tích cú pháp hiệu quả cho giao diện này sẽ như thế nào?
Bernd

Trong trường hợp bạn muốn kiểm tra xem một đối tượng thuộc một lớp nào đó, hãy xem câu trả lời này .
ý nghĩa-vấn đề

Câu trả lời:


529

Chuỗi từ một thể hiện :

String(describing: YourType.self)

Chuỗi từ một loại :

String(describing: self)

Thí dụ:

struct Foo {

    // Instance Level
    var typeName: String {
        return String(describing: Foo.self)
    }

    // Instance Level - Alternative Way
    var otherTypeName: String {
        let thisType = type(of: self)
        return String(describing: thisType)
    }

    // Type Level
    static var typeName: String {
        return String(describing: self)
    }

}

Foo().typeName       // = "Foo"
Foo().otherTypeName  // = "Foo"
Foo.typeName         // = "Foo"

Đã thử nghiệm với class, structenum.


4
Đây đúng là tình trạng đó phải không? Theo các tài liệu về Chuỗi cho trình khởi tạo này, "một kết quả không xác định được cung cấp tự động bởi thư viện chuẩn Swift" khi nó không tuân thủ Streamable, hoặc CustomStringConvertible hoặc CustomDebugStringConvertible. Vì vậy, trong khi nó có thể được hiển thị giá trị mong muốn bây giờ, nó sẽ tiếp tục làm như vậy trong tương lai?
Tod Cickyham

3
Sử dụng điều này trong Xcode 7.3.1 & Swift 2.2 tạo ra những điều sau đây, điều này không lý tưởng. '' let name = String (self) name String "<MyAppName.MyClassName: 0x ########>" ''
CodeBender

13
Trong Swift 3, câu trả lời thích hợp là gọi String(describing: MyViewController.self)như đã lưu ý trong một vài câu trả lời dưới đây.
AmitaiB

10
Nhưng nó trả về "MyViewControll.TYPE"!
orkenstein

3
@ RudolfAdamkovič Vâng, đó là sự thật. Nhưng tôi không thích viết tên lớp một cách rõ ràng (nếu tôi đổi tên lớp Foothành Foo2nhưng tạo một lớp Fooở nơi khác, điều đó có thể làm mất mã). Trong trường hợp phân lớp, bạn cần sử dụng type(of:)hoặc Type.selftùy thuộc vào nhu cầu của bạn. Có lẽ type(of:)là thích hợp hơn để có được tên lớp.
Marián Černý

182

CẬP NHẬT ĐẾN 5

Chúng ta có thể nhận được các mô tả đẹp về tên loại bằng cách sử dụng biến thể hiện thông qua trình Stringkhởi tạo và tạo các đối tượng mới của một lớp nhất định

Giống như, ví dụ print(String(describing: type(of: object))). Trường hợp objectcó thể là một biến đối tượng như mảng, từ điển, an Int, a NSDate, v.v.

Bởi vì NSObjectlà lớp gốc của hầu hết các cấu trúc phân cấp của lớp Objective-C, bạn có thể thử tạo một phần mở rộng NSObjectđể lấy tên lớp của mỗi lớp con của NSObject. Như thế này:

extension NSObject {
    var theClassName: String {
        return NSStringFromClass(type(of: self))
    }
}

Hoặc bạn có thể tạo một chức năng tĩnh có tham số là loại Any(Giao thức mà tất cả các loại tuân thủ ngầm) và trả về tên lớp là Chuỗi. Như thế này:

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

Bây giờ bạn có thể làm một cái gì đó như thế này:

class ClassOne : UIViewController{ /* some code here */ }
class ClassTwo : ClassOne{ /* some code here */ }

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Get the class name as String
        let dictionary: [String: CGFloat] = [:]
        let array: [Int] = []
        let int = 9
        let numFloat: CGFloat = 3.0
        let numDouble: Double = 1.0
        let classOne = ClassOne()
        let classTwo: ClassTwo? = ClassTwo()
        let now = NSDate()
        let lbl = UILabel()

        print("dictionary: [String: CGFloat] = [:] -> \(Utility.classNameAsString(dictionary))")
        print("array: [Int] = [] -> \(Utility.classNameAsString(array))")
        print("int = 9 -> \(Utility.classNameAsString(int))")
        print("numFloat: CGFloat = 3.0 -> \(Utility.classNameAsString(numFloat))")
        print("numDouble: Double = 1.0 -> \(Utility.classNameAsString(numDouble))")
        print("classOne = ClassOne() -> \((ClassOne).self)") //we use the Extension
        if classTwo != nil {
            print("classTwo: ClassTwo? = ClassTwo() -> \(Utility.classNameAsString(classTwo!))") //now we can use a Forced-Value Expression and unwrap the value
        }
        print("now = Date() -> \(Utility.classNameAsString(now))")
        print("lbl = UILabel() -> \(String(describing: type(of: lbl)))") // we use the String initializer directly

    }
}

Ngoài ra, một khi chúng ta có thể lấy tên lớp là String, chúng ta có thể khởi tạo các đối tượng mới của lớp đó :

// Instantiate a class from a String
print("\nInstantiate a class from a String")
let aClassName = classOne.theClassName
let aClassType = NSClassFromString(aClassName) as! NSObject.Type
let instance = aClassType.init() // we create a new object
print(String(cString: class_getName(type(of: instance))))
print(instance.self is ClassOne)

Có lẽ điều này giúp ai đó ngoài kia!.


2
Đây phải là câu trả lời được chấp nhận. Cảm ơn, tôi đã tìm cách in tên lớp mà không cần phải khởi tạo nó, và tìm thấy câu trả lời ở đây. Cảm ơn in (Chuỗi (BaseAsyncTask))
Bruno Coelho

4
classNameAsStringcó thể được đơn giản hóa className, với "tên" đó ngụ ý loại của nó. theClassNamelà simillar cho câu chuyện của facebook, được đặt tên ban đầu là thefacebook.
DawnSong

"Diccionary" ... phiên bản tiếng Ý của bộ sưu tập từ điển của Swift:]
Andrew Kirna

Giải pháp này chuẩn bị tên dự án cho mỗi classTag, nghĩa là ProjectTest.MyViewController. Làm thế nào để tôi thoát khỏi tên dự án này? Tôi chỉ muốn tên lớp.
Sazzad Hissain Khan

132

Swift 5

Đây là phần mở rộng để lấy typeNamebiến dưới dạng (làm việc với cả loại giá trị hoặc loại tham chiếu).

protocol NameDescribable {
    var typeName: String { get }
    static var typeName: String { get }
}

extension NameDescribable {
    var typeName: String {
        return String(describing: type(of: self))
    }

    static var typeName: String {
        return String(describing: self)
    }
}

Cách sử dụng:

// Extend with class/struct/enum...
extension NSObject: NameDescribable {}
extension Array: NameDescribable {}
extension UIBarStyle: NameDescribable { }

print(UITabBarController().typeName)
print(UINavigationController.typeName)
print([Int]().typeName)
print(UIBarStyle.typeName)

// Out put:
UITabBarController
UINavigationController
Array<Int>
UIBarStyle

12
Bất cứ ý tưởng tại sao tôi nhận được MyAppName.MyClassName từ đó? Tôi chỉ quan tâm đến MyClassName
Andrew

@Andrew bạn có thể cung cấp cụ thể hơn trong trường hợp của bạn? Tôi đang sử dụng tiện ích mở rộng này trong nhiều dự án và tất cả chúng đều đáp ứng như dự định.
nahung89

1
Chà, nếu tôi gọi nó từ chính lớp thì nó trả về MyClassName, nhưng nếu tôi gói nó trong một phương thức trả về chuỗi tên lớp và gọi nó từ một lớp khác thì nó trả về MyAppName.MyClassName. Trong khi tìm kiếm xung quanh tôi thấy rằng tất cả các lớp đều được đặt tên ngầm, vì vậy nếu bạn gọi nó bên ngoài lớp, bạn sẽ nhận được MyAppName.MyClassName thay vì MyClassName. Sau đó, bạn phải dựa vào thao tác chuỗi để có được tên lớp.
Andrew

1
tiện ích mở rộng rất hữu ích
Hattori Hanzou

@Andrew, không chắc chắn về hoạt động hiện tại, nhưng tôi nhớ, String (mô tả: type (of: self)) được sử dụng để trả về ModuleName.ClassName tại một điểm. Tôi đã chia dựa trên. nhân vật và lấy các đối tượng cuối cùng.
BangOperator

31

Swift 3.0

String(describing: MyViewController.self)


2
Thực tế type(of: )là quá mức cần thiết, String(describing: MyViewController.self)sẽ đủ
Alexander Vasenin

2
Điều này tạo ra một chuỗi như <App_Name.ClassName: 0x79e14450> đối với tôi, không lý tưởng
Doug Amos

Phải không String.init(describing: MyViewController.self)?
Iulian Onofrei

2
@IulianOnofrei, Bạn có thể làm điều đó, nhưng initkhông cần thiết.
shim

Hoàn toàn thoát khỏi tiền tố AppName! Cảm ơn
yuchen

28

Tôi đề nghị một cách tiếp cận như vậy ( rất Swifty ):

// Swift 3
func typeName(_ some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(type(of: some))"
}

// Swift 2
func typeName(some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)"
}

Nó không sử dụng cả nội tâm lẫn cách gỡ rối thủ công ( không có phép thuật! ).


Đây là một bản demo:

// Swift 3

import class Foundation.NSObject

func typeName(_ some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(type(of: some))"
}

class GenericClass<T> {
    var x: T? = nil
}

protocol Proto1 {
    func f(x: Int) -> Int
}


@objc(ObjCClass1)
class Class1: NSObject, Proto1 {
    func f(x: Int) -> Int {
        return x
    }
}

struct Struct1 {
    var x: Int
}

enum Enum1 {
    case X
}

print(typeName(GenericClass<Int>.self)) // GenericClass<Int>
print(typeName(GenericClass<Int>()))  // GenericClass<Int>

print(typeName(Proto1.self)) // Proto1

print(typeName(Class1.self))   // Class1
print(typeName(Class1())) // Class1
print(typeName(Class1().f)) // (Int) -> Int

print(typeName(Struct1.self)) // Struct1
print(typeName(Struct1(x: 1))) // Struct1
print(typeName(Enum1.self)) // Enum1
print(typeName(Enum1.X)) // Enum1

Họ đã thay đổi nó trong Swift 3.0. Bây giờ bạn có thể nhận chuỗi từtype(of: self)
Asad Ali

Đã cập nhật lên Swift 3.
werediver 11/03/2017

Tôi nghĩ rằng đây là cách tốt nhất để có được tên loại: bất kỳ trường hợp nào của lớp, dựa trên NSObject, enum, typealiase, hàm, thuộc tính var và bất kỳ loại nào, thậm chí là hằng. Đối với các loại, bạn phải sử dụng. Mình. Ví dụ: typeName (Int.elf), typeName (true.elf). Bạn cũng không cần phải lo lắng về tên dự án như một phần (chẳng hạn như AppProj. [Tên chữ]) Tuyệt vời!
David.Chu.ca

23

Nếu bạn có loại Foo, đoạn mã sau sẽ cung cấp cho bạn "Foo"trong Swift 3 và Swift 4:

let className = String(describing: Foo.self) // Gives you "Foo"

Vấn đề với hầu hết các câu trả lời ở đây là chúng cung cấp cho bạn "Foo.Type"dưới dạng chuỗi kết quả khi bạn không có bất kỳ trường hợp nào của loại, khi điều bạn thực sự muốn chỉ là "Foo". Sau đây cung cấp cho bạn "Foo.Type", như được đề cập trong một loạt các câu trả lời khác.

let className = String(describing: type(of: Foo.self)) // Gives you "Foo.Type"

Phần type(of:)này là không cần thiết nếu bạn chỉ muốn "Foo".


21

Trong Swift 4.1 và giờ là Swift 4.2 :

import Foundation

class SomeClass {
    class InnerClass {
        let foo: Int

        init(foo: Int) {
            self.foo = foo
        }
    }

    let foo: Int

    init(foo: Int) {
        self.foo = foo
    }
}

class AnotherClass : NSObject {
    let foo: Int

    init(foo: Int) {
        self.foo = foo
        super.init()
    }
}

struct SomeStruct {
    let bar: Int

    init(bar: Int) {
        self.bar = bar
    }
}

let c = SomeClass(foo: 42)
let s = SomeStruct(bar: 1337)
let i = SomeClass.InnerClass(foo: 2018)
let a = AnotherClass(foo: 1<<8)

Nếu bạn không có ví dụ xung quanh:

String(describing: SomeClass.self) // Result: SomeClass
String(describing: SomeStruct.self) // Result: SomeStruct
String(describing: SomeClass.InnerClass.self) // Result: InnerClass
String(describing: AnotherClass.self) // Result: AnotherClass

Nếu bạn có một ví dụ xung quanh:

String(describing: type(of: c)) // Result: SomeClass
String(describing: type(of: s)) // Result: SomeStruct
String(describing: type(of: i)) // Result: InnerClass
String(describing: type(of: a)) // Result: AnotherClass

14

Để lấy tên của một lớp Swift từ một đối tượng, ví dụ: đối tượng var: someClass (), hãy sử dụng

String(describing: type(of: object))

Để lấy tên của một lớp Swift từ một loại lớp, ví dụ: Một số lớp, hãy sử dụng:

String(describing: SomeClass.self)

Đầu ra:

"Một số kính"


13

Bạn có thể thử theo cách này:

self.classForCoder.description()

Điều này hoạt động rất tốt vì nó bao gồm toàn bộ "không gian tên" (tiền tố thành viên mục tiêu phù hợp).
BonanzaDriver

BTW, tôi cũng đã tìm thấy bằng cách sử dụng "Chuỗi (tự)" trong đó bản thân là một phiên bản của MyViewControll cũng hoạt động ... nó cung cấp thông tin tương tự ... Xcode 7.1.
BonanzaDriver

12

Bạn có thể sử dụng chức năng thư viện chuẩn Swift được gọi _stdlib_getDemangledTypeNamenhư thế này:

let name = _stdlib_getDemangledTypeName(myViewController)

@NeilJaff bạn có ý gì khi chỉ "một chuỗi tùy chọn"? Nó trả về một chuỗi và nó không phải là tùy chọn.
Jernej Giorgner 20/03/2015

3
Cách tiếp cận này không trả về tên của các lớp trong API riêng FYI. Nó sẽ trả về tên của tên lớp cơ sở công cộng đầu tiên.
Tylerc230

Tôi nhận được thông báo: Sử dụng số nhận dạng chưa được giải quyết '_stdlib_getDemangledTypeName'
Adobels

12

Để lấy tên loại dưới dạng chuỗi trong Swift 4 (Tôi chưa kiểm tra các phiên bản trước đó), chỉ cần sử dụng phép nội suy chuỗi:

"\(type(of: myViewController))"

Bạn có thể sử dụng .selftrên một loại chính nó và type(of:_)chức năng trên một ví dụ:

// Both constants will have "UIViewController" as their value
let stringFromType = "\(UIViewController.self)"
let stringFromInstance = "\(type(of: UIViewController()))"

8

Người ta cũng có thể sử dụng gương:

let vc = UIViewController()

String(Mirror(reflecting: vc).subjectType)

NB: Phương pháp này cũng có thể được sử dụng cho Structs và Enums. Có một displayStyle cho biết loại cấu trúc nào:

Mirror(reflecting: vc).displayStyle

Sự trở lại là một enum để bạn có thể:

Mirror(reflecting: vc).displayStyle == .Class

Cảm ơn. Đó là những gì làm việc cho tôi. Điều duy nhất (ít nhất là trên iOS 9) là objectType đã kết thúc bằng ".Type", vì vậy tôi phải xóa phần đó khỏi chuỗi.
Nikolay Suvandzhiev

8

Swift 5.1

Bạn có thể nhận được các tên lớp, struct, enum, giao thức và NSObject Self.self.

print("\(Self.self)")

Lưu ý rằng điều này có thể đặt trước tên mô-đun (khác với String(describing: type(of: self)))
Ixx

7

Swift 3.0: Bạn có thể tạo một tiện ích mở rộng như thế này .. Nó trả lại tên lớp mà không cần tên dự án

extension NSObject {
    var className: String {
        return NSStringFromClass(self as! AnyClass).components(separatedBy: ".").last ?? ""
    }

    public class var className: String {
        return NSStringFromClass(self).components(separatedBy: ".").last ?? ""
    }
}

Khi tôi cố gắng sử dụng, tôi gặp lỗi: Không thể truyền giá trị của loại 'ProjectName.MyViewControll' (0x1020409e8) thành 'Swift.AnyObject.Type' (0x7fb96fc0dec0).
tylerSF

6

Bạn có thể mở rộng NSObjectProtocoltrong Swift 4 như thế này:

import Foundation

extension NSObjectProtocol {

    var className: String {
        return String(describing: Self.self)
    }
}

Điều này sẽ làm cho biến tính toán classNamecó sẵn cho TẤT CẢ các lớp. Sử dụng này trong một print()in CalendarViewControllersẽ in "CalendarViewController"trong giao diện điều khiển.


4

Tôi đã tìm kiếm câu trả lời này trong một thời gian. Tôi sử dụng GKStateMachine và muốn quan sát các thay đổi trạng thái và muốn một cách dễ dàng để chỉ nhìn thấy tên lớp. Tôi không chắc đó chỉ là iOS 10 hay Swift 2.3, nhưng trong môi trường đó, những điều sau đây thực hiện chính xác những gì tôi muốn:

let state:GKState?
print("Class Name: \(String(state.classForCoder)")

// Output:    
// Class Name: GKState

4

Bạn có thể lấy tên của lớp đang làm một cái gì đó như:

class Person {}
String(describing: Person.self)

3

Để có được tên lớp là String, hãy khai báo lớp của bạn như sau

@objc(YourClassName) class YourClassName{}

Và lấy tên lớp bằng cú pháp sau

NSStringFromClass(YourClassName)

3

Thử reflect().summary trên ClassType hoặc thể hiện động. Unwrap tùy chọn trước khi nhận DynamicType nếu không DynamicType là trình bao bọc Tùy chọn.

class SampleClass { class InnerClass{} }
let sampleClassName = reflect(SampleClass.self).summary;
let instance = SampleClass();
let instanceClassName = reflect(instance.dynamicType).summary;
let innerInstance = SampleClass.InnerClass();
let InnerInstanceClassName = reflect(innerInstance.dynamicType).summary.pathExtension;
let tupleArray = [(Int,[String:Int])]();
let tupleArrayTypeName = reflect(tupleArray.dynamicType).summary;

Tóm tắt là một đường dẫn lớp với các loại chung được mô tả. Để có được một tên lớp đơn giản từ tóm tắt, hãy thử phương pháp này.

func simpleClassName( complexClassName:String ) -> String {
    var result = complexClassName;
    var range = result.rangeOfString( "<" );
    if ( nil != range ) { result = result.substringToIndex( range!.startIndex ); }
    range = result.rangeOfString( "." );
    if ( nil != range ) { result = result.pathExtension; }
    return result;
}

3

Các giải pháp trên không hiệu quả với tôi. Sản phẩm chủ yếu được đề cập trong một số ý kiến:

MyAppName.ClassName

hoặc là

MyFrameWorkName.ClassName

Giải pháp này hoạt động trên XCode 9, Swift 3.0:

Tôi đặt tên cho nó classNameCleanedđể dễ truy cập hơn và không xung đột với className()những thay đổi trong tương lai :

extension NSObject {

static var classNameCleaned : String {

    let className = self.className()
    if className.contains(".") {
        let namesArray = className.components(separatedBy: ".")
        return namesArray.last ?? className
    } else {
        return self.className()
    }

}

}

Sử dụng:

NSViewController.classNameCleaned
MyCustomClass.classNameCleaned

2

Swift 3.0 (macOS 10.10 trở lên), bạn có thể lấy nó từ className

self.className.components(separatedBy: ".").last!

1
Theo như tôi có thể nói là không có thuộc tính className dựng sẵn trong các lớp Swift. Bạn đã phân lớp từ một cái gì đó?
shim

@shim className được khai báo trong Foundation nhưng có vẻ như nó chỉ khả dụng trên macOS 10.10 trở lên. Tôi chỉ chỉnh sửa câu trả lời của tôi.
Thanh Vũ Trần

1
Hừm, thật lạ. Tại sao họ không bao gồm nó trong iOS? Cũng lưu ý rằng đây là một phương thức ví dụ trên NSObject developer.apple.com/reference/objectivec/nsobject/iêu
shim

2

Tôi đã thử type(of:...)trong Playground với Swift 3. Đây là kết quả của tôi. Đây là phiên bản định dạng mã.

print(String(describing: type(of: UIButton.self)))
print(String(describing: type(of: UIButton())))
UIButton.Type
UIButton

Thật lãng phí khi khởi tạo một thể hiện chỉ để xác định tên lớp.
sethfri

@sethfri Mình dùng cái này để xác định kiểu động.
Lumialxk

Tôi vẫn không hiểu tại sao bạn cần tạo một cá thể chỉ để lấy thông tin loại của lớp
sethfri


2

Đây là loại ví dụ cho lớp var. Không bao gồm tên của gói.

extension NSObject {
    class var className: String {
        return "\(self)"
    }
}

1

Nếu bạn không thích tên được đọc sai, bạn có thể ra lệnh cho tên của chính mình:

@objc(CalendarViewController) class CalendarViewController : UIViewController {
    // ...
}

Tuy nhiên, về lâu dài sẽ tốt hơn khi học cách phân tích tên xéo. Các định dạng là tiêu chuẩn và có ý nghĩa và sẽ không thay đổi.


Không, không phải vậy, đối với lớp tôi, nó trả về (ExistentialMetatype) - dựa vào hành vi thời gian chạy không có giấy tờ luôn nguy hiểm, đặc biệt là đối với swift vì nó vẫn còn ở giai đoạn khá sớm.
superarts.org

1

Đôi khi các giải pháp khác sẽ đưa ra một tên không hữu ích tùy thuộc vào đối tượng bạn đang cố gắng xem xét. Trong trường hợp đó, bạn có thể lấy tên lớp dưới dạng chuỗi bằng cách sử dụng như sau.

String(cString: object_getClassName(Any!))

Nhấp vào chức năng trong xcode để xem một số phương thức liên quan khá hữu ích. hoặc kiểm tra tại đây https://developer.apple.com/reference/objectivec/objective_c_fifts


1

Swift 5 Bạn có thể lấy class_name dưới dạng một chuỗi với Chuỗi (mô tả: Self.elf)

print(String(describing: Self.self))

0

Tôi sử dụng nó trong Swift 2.2

guard let currentController = UIApplication.topViewController() else { return }
currentController.classForCoder.description().componentsSeparatedByString(".").last!

0

Swift 5.1: -

Bạn cũng có thể sử dụng hàm chung để lấy tên lớp của đối tượng dưới dạng chuỗi

struct GenericFunctions {
 static func className<T>(_ name: T) -> String {
        return "\(name)"
    }

}

Gọi chức năng này bằng cách sử dụng như sau: -

let name = GenericFunctions.className(ViewController.self)

Chúc mừng mã hóa :)


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.