19

I am aware of this question regarding how we can get a readable class name of an objective-c class in Swift.

What I want to achieve is getting the readable class name of a Swift class from inside objective-c without mangling the class name with the module.

So if I have a Swift class:

class Foo: NSObject{}

Then inside Objective-C I would love to use the convenient NSStringFromClass to convert the class name to a string.

I would expect NSStringFromClass([Foo class]) to return @"Foo" but instead it returns @"Bar.Foo" withBarbeing the module name.

I came across this Gist but it seems a little hacky and messy, is there a better way? Something that doesn't include typing the class name manually into a string would be preferred.

Community
  • 1
  • 1
Daniel Galasko
  • 23,617
  • 8
  • 77
  • 97
  • The module-prefixed name *is* the readable class name. It's possible to have multiple classes with the same name if you remove the module prefix. You should preserve it. – Greg Parker Nov 19 '14 at 07:09
  • @GregParker that doesn't work with storyboards or core data so there are scenarios where this is merited:) Something like nibWithName:NSStringFromClass: – Daniel Galasko Nov 19 '14 at 07:11
  • The recommendation for -nibWithName: is to use the module-prefixed name as the file name too. – Greg Parker Nov 19 '14 at 07:18
  • @GregParker do you have any links you can point to for that? This seems to defeat the whole notion of modules since i thought we have already dropped the 3 letter prefix, seems overkill to have to name everything by module name. And if you change module name then thats a huge necessary refactor – Daniel Galasko Nov 19 '14 at 07:26

7 Answers7

39

BEFORE SWIFT 2.1:

Just put @objc(YourClassName) in your swift class:

@objc(YourClassName)

class YourClassName: NSObject {

}

And you can use NSStringFromClass like this:

NSStringFromClass(YourClassName.self)

It should also work from Objective-C then.


SWIFT 2.1

With Swift 2.1 a comment to this answer states that this is sufficient:

class YourClassName: NSObject {

}

And just use:

var str = String(YourClassName)

I have not tested this from Objective-C code myself though.


There's been a edit-suggestions that want to use this instead for Swift 4:

var str = String(describing: YourClassName.self)

I've not tested this from Objective-C though.

ullstrm
  • 9,812
  • 7
  • 52
  • 83
  • 1
    FYI: When setting `@objc(YourClassName)` on a subclass of `UIView` that is set in a xib file, I had to re-set the class. – martn_st Jul 23 '15 at 20:47
  • You might wanna update your answer following latest accepted syntax http://stackoverflow.com/a/35765422/1652402 – Daniel Galasko Mar 03 '16 at 07:29
  • @DanielGalasko thank you. It seems to be working well in playground. Have you tried it from objective-C aswell? – ullstrm Mar 03 '16 at 07:40
  • 1
    Um, this has nothing to do with Obj-C. Merely that in Swift 2.1 the accepted syntax is `String(ClassName)` over `NSStringFromClass(ClassName)` – Daniel Galasko Mar 03 '16 at 07:44
  • 3
    The question that is answered is: "Then inside Objective-C I would love to use the convenient NSStringFromClass to convert the class name to a string." So it has everything to do with Objective-C. – ullstrm Mar 03 '16 at 08:02
  • This is correct for Swift but it doesn't answer the question that you posted Daniel as @ullstrm pointed out: "getting the readable class name of a Swift class from inside objective-c". I know this was your own question, but maybe you should then update it to mention you are fine getting it inside Swift instead. Even though it is hacky, oskarko answer seems to be the closest. – agirault Feb 05 '19 at 20:54
  • Please see my answer, this is not recommended practice and _absolutely_ should not be done this way. – TheCodingArt Dec 31 '19 at 16:36
11

The reason you get "Bar.Foo" is because swift is a namespace language. I'm assuming the application you were running this in was named Bar. Hence you may get your class name using the following:

let nameSpaceClassName = NSStringFromClass(Foo)
let className = nameSpaceClassName.componentsSeparatedByString(".").last! as String

Edit:

The above is an extremely old answer that should be changed based on more recent introductions in the Swift library (from 3 and 4 at least). Note the following use of the Mirroring interfaces to properly extract a class's metadata:

import Foundation

/// A protocol that provides demangled information regarding core Swift structure naming components in a usable way.
public protocol StructureNameReflectable {
    /**
     An ordered list of domain named based components for the structure.
     - Example: A simple example would be:
     ```
     class Dog: NSObject {
        class Retriever: NSOjbect {}
    }
     let retriever = Retriever()
     ```
     
     `retriever.structuredName` would output `["Dog", "Retriever"]`
 */
    static var structureNameComponents: [String] { get }
    
    ///Outputs the structure's name in a string based form minus namespacing
    static var structureName: String { get }
    
    ///Outputs the structure's name in a string based form with namespacing
    static var namespacedStructureName: String { get }
    
    ///Outputs the bundle the structure is contained in
    static var bundle: Bundle { get }
}

extension StructureNameReflectable {
    
    public static var structureNameComponents: [String] {
        let type = Mirror(reflecting: self).subjectType
        let structureNameComponents = "\(type)".components(separatedBy: ".")
        return structureNameComponents
    }
    
    public static var structureName: String {
        var structureNameComponents = self.structureNameComponents
        
        if structureNameComponents.count > 1 && structureNameComponents.last == "Type" {
            structureNameComponents.removeLast()
        }

        return structureNameComponents.last!
    }
    
    public static var namespacedStructureName: String {
        return structureNameComponents.joined(separator: ".")
    }
}

extension StructureNameReflectable where Self: NSObject {
    public static var bundle: Bundle {
        return Bundle(for: self)
    }
    
    public static var className: String {
        return structureName
    }
}

extension NSObject: StructureNameReflectable { }

Hopefully the above will give guidance for the consistently improper use of String(describing:).

TheCodingArt
  • 3,436
  • 4
  • 30
  • 53
  • 2
    Alternatively one could also call nameSpaceClassName.pathExtension to get the class name but I still prefer the accepted answer:) – Daniel Galasko Feb 09 '15 at 07:04
  • 1
    However, `String(Foo)` is more suitable. – DawnSong Apr 14 '16 at 11:40
  • 1
    @DawnSong no it isn't, that will print out the description which is never guaranteed to be the class name or formatted in that structure. There's a difference between being explicit and non explicit while making assumptions – TheCodingArt Apr 14 '16 at 12:12
  • If this is hard documented as expected behavior then that's one thing, if it "just works" with a specific swift version, that's a bad assumption. – TheCodingArt Apr 14 '16 at 12:13
  • Also, it seems you're still inheriting from NSObject – TheCodingArt Apr 14 '16 at 12:15
  • What you said is not exactly right, `String.init(reflecting: T) ` and `String.init(T)` are related to my answer. https://developer.apple.com/library/watchos/documentation/Swift/Reference/Swift_String_Structure/index.html#//apple_ref/swift/structctr/String/s:FSScurFT10reflectingx_SS – DawnSong Apr 15 '16 at 11:35
  • Further more, `Array.last` is not available for iOS 8, you have to convert it to NSArray and to use `lastObject`, so `(NSStringFromClass(Foo) as NSString).pathExtension` is more suitable. – DawnSong Apr 15 '16 at 11:38
  • @DawnSong what do you mean? It's stated right here in the parameter as a conformance to CustomStringConvertable. – TheCodingArt Apr 15 '16 at 12:08
  • My comments very strongly still apply – TheCodingArt Apr 15 '16 at 12:09
  • There's also this: "Otherwise, an unspecified result is supplied automatically by the Swift standard library." Which basically means different results for different classes based off of implementation. You can not guarantee what will happen by default. It's a really bad assumption and bad practice to build an assumption that this will always return what you want. – TheCodingArt Apr 15 '16 at 12:10
  • As a side note, my answer was written with Swift 1.1 or 1.2 in mind. iOS 8 has nothing to do with the type casting, that's a change in a newer version of Swift. – TheCodingArt Apr 15 '16 at 12:17
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/109254/discussion-between-dawn-song-and-thecodingart). – DawnSong Apr 15 '16 at 13:07
6

In objective-c your best option is:

NSString *yourCellName = NSStringFromClass([yourCell class]).pathExtension;
oskarko
  • 3,382
  • 1
  • 26
  • 26
2

Working solution for this in Swift 2.1 is

String(MyClass)
ZaEeM ZaFaR
  • 1,508
  • 17
  • 22
  • `String.init(reflecting: T) ` is the init function prototype, see also `String.init(T)`. https://developer.apple.com/library/watchos/documentation/Swift/Reference/Swift_String_Structure/index.html#//apple_ref/swift/structctr/String/s:FSScurFT10reflectingx_SS – DawnSong Apr 15 '16 at 13:02
  • I don't know why somebody down voted this answer, and did not give his or her reason. – DawnSong Apr 15 '16 at 13:03
1

Swift 5.1 solution for NSStringFromClass

If you want the string description of a class name (say CustomTableViewCell) use:

String(describing: CustomTableViewCell.self)
Jim Tierney
  • 4,078
  • 3
  • 27
  • 48
0

On the latest Swift below worked for me:

NSStringFromClass(type(of: yourClass!.self))
Nandish
  • 1,136
  • 9
  • 16
-2

Try it with Swift 2.0:

self.description().componentsSeparatedByString(".").last!
Tai Le
  • 8,530
  • 5
  • 41
  • 34
  • Class names aren't files, they don't have paths or extensions. – Hyperbole Aug 31 '15 at 16:54
  • It's a cleaver use of the string class functionality. A swift class is prepended with the module name such as Module.Class. Using pathExtension might be a misnomer, but does basically return the string separated by the period. – Martin-Gilles Lavoie Oct 12 '21 at 14:16