Relying on String(describing:) to get the name of a type

Hi,

I'm wondering about using String(describing: t) (or "\(t)"), where t is a type, to get the name of that type for use cases like the following:

static func findStoryboard() -> UIStoryboard {
    return UIStoryboard(name: String(describing: self), bundle: nil).
}

From what I've been able to find, this is behavior that a lot of code relies on, but it isn't a guarantee that is documented anywhere. In a similar topic, Jordan Rose writes:

String(describing:) prints a reasonably displayable form of the type name. String(reflecting:) preserves a lot more information to uniquely identify the type. Right now, the former doesn't include qualification and the latter does. This is behavior we could change, but we might need to be pretty careful about changing because people could be depending on it.

My questions:

  1. Would you recommend such code, or it there a reality that it in the future will break?
  2. Is there a better way to do it?
  3. If this indeed would change in the future, would that be a change that happens compile-time or run-time? I.e. would an app compiled today be safe for future changes? I would assume that to be true at least for as long as we're bundling Swift, but do I need to be concerned that I leave such code in an app, in 2020 it is compiled against system-bundled Swift and in 2022 a published app breaks?

Regards, Simon

  1. Would you recommend such code, or it there a reality that it in the
    future will break?

I personally wouldn’t use this technique.

  1. Is there a better way to do it?

If you’re doing this for storyboards, you’re already knee deep in the Objective-C runtime, and thus one alternative is to get the Objective-C runtime name for the class.

class MyClassName : NSObject {
}

print(NSStringFromClass(MyClassName.self))
// -> MyModuleName.MyClassName

I’d be comfortable relying on that.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

3 Likes

Ok, thanks for the reply. Using NSStringFromClass sounds fine with me, although a bit more inconvenient since I would have to add logic to get the last part after the dot... Would I be safe with just something like this?

String(NSStringFromClass(self).split(separator: ".").last!)

Regards, Simon

Would I be safe with just something like this?

I believe so.

My rationale for this is that, if you look inside a resource file (nib or storyboard) you’ll see that MyModuleName.MyClassName is the syntax used by that resource file to reference a Swift class. The UI frameworks interpreting such a resource file use the Objective-C runtime to look up the class from that string, and thus Swift classes have to be presented to the Objective-C runtime in that way.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

You could also just make sure that the Objective-C runtime’s name is usable as-is by doing this (adapting @eskimo’s example):

@objc(MyClassName)
class MyClassName : NSObject {
}

print(NSStringFromClass(MyClassName.self))
// -> MyClassName

@eskimo Great, thanks! This makes sense to me as well.

@marco.masser That is a good thing to be aware of, although I'd prefer not to have to write that everywhere!

Actually, I came to think now – if one wanted to be very "correct", one could use the first part (MyModuleName) to identify the Bundle and look it up with Bundle.init?(identifier:), and send that to the UIStoryboard initializer. Most probably overkill since I will mostly use this for code in the main bundle. But it would feel kinda nice. :slightly_smiling_face:

That’s a matter of preference, of course. I like to be explicit about these things. When I want to look something up in the Objective-C runtime using a string, I like to know what that string is exactly and not rely on heuristics like that. Although splitting the string is very unlikely to break, to be honest.

Not quite. The Swift module name doesn’t have anything to do with a bundle identifier. In most cases, it’s probably the name of the bundle’s executable.

I don’t have much experience with iOS, but on macOS Bundle(identifier: String) expects a, well, bundle identifier (reverse DNS style) of a bundle that was already initialized before, i.e. one that was loaded by a different mechanism.
If you want to be “correct”, you’d have to get the class name first, then get the class using NSClassFromString() and then use Bundle(for: AnyClass).
That still assumes that the storyboard is located in the same bundle as the class, but that’s probably a very safe bet :grinning:

Yeah, there's always a trade-off between explicit and implicit strategies; I fully understand your preference. Although it does get a little odd in this case to be explicit about the ObjC-mapped name and then get that name for implicit Storyboard lookup, I think – in that case I'd prefer to explicitly name the storyboard directly. :slight_smile:

Ah, you're right of course.

Or just use Bundle(for: self), since a class type is what I'm starting with. :slight_smile:

Of course :man_facepalming:

1 Like