Can forwarding subclass methods to generic methods be avoided?

I use this combo (I only ever actually find myself calling the method on top but do want the storyboard parameter publicly available for flexibility):

public extension UIViewController {
  static func instantiate() -> Self? {
    return UIViewController.instantiate()
  }
  
  static func instantiate<ViewController: UIViewController>(
    storyboard: UIStoryboard = UIStoryboard(
      name: "\(ViewController.self)",
      bundle: nil
    )
  ) -> ViewController? {
    return storyboard.instantiateInitialViewController() as? ViewController
  }
}

but what I actually want is just one method, which, because casting to Self isn't allowed directly, might look like this:

static func instantiate(
  storyboard: UIStoryboard = UIStoryboard(
    name: String(describing: self),
    bundle: nil
  )
) -> Self? {
  func instantiate<ViewController: UIViewController>()-> ViewController? {
    return storyboard.instantiateInitialViewController() as? ViewController
  }
  return instantiate()
}

Trouble is, while String(describing: self) is the same as "\(ViewController.self)" at runtime, it doesn't yield the proper compile-time result. Is this two-method solution the best we've got?

Taking a step back, I gotta say that I’d not be confortable using String(describing:) in this way. Or "\(ViewController.self)" for that matter. To me these techniques seem to be closely tied to text output for debugging purposes, and thus shouldn’t be used at runtime. Consider this last quote from the String(describing:) documentation:

An unspecified result is supplied automatically by the Swift
standard library.

You could imagine a world where String(describing:) starts returning a slightly different value for class parameters, breaking your code at runtime [1].

In you’re shoes I’d use NSStringFromClass for this. This string is already used in storyboards (it’s how the nibs save a reference to the class of the objects they contain) and thus I’d expect it to not change format.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

[1] Currently this isn’t a problem because in Swift 4 the standard library is bundled with your app, and thus they can’t get out of sync. Once you update to Swift 5 that’ll no longer be the case.

1 Like

I think NSStringFromClass is also not the desired solution for this problem. I'm using a similar pattern where I use the metatype to obtain the type name without the parent types.

Consider the following playground example:

class A {
  class B {}
}

let a = NSStringFromClass(A.self)
let b = NSStringFromClass(A.B.self)
print(a, b, "\(A.self) \(A.B.self)")

This prints __lldb_expr_11.A _TtCC14__lldb_expr_111A1B A B. In my case I want to obtain just A and B and not need to parse/demangle some complex type string.

Consider the following playground example:

Playground are weird. In a real program this will print:

MyModule.A _TtCC8MyModule1A1B A B

Personally I think that MyModule.A is fine, while _TtCC8MyModule1A1B is definitely suboptimal (-: However, for the use case described by the original poster, nested classes are unlikely.

In my case I want to obtain just A and B

Right, and that’s central to my point: There’s nothing about the String(describing:) API contract that guarantees those results. You could just as easily get back MyModule.A and MyModule.A.B.

The only way to nail down these values would be to change the API contract for String(describing:) or introduce a new API that does this specific thing.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

I think we should go this route, because it will preserve source compatibility and from the users perspective we only get an official guarantee that metatypes will be converted as mentioned above and only the documentation will be updated. Behind the scenes we'd enforce the conversion and add some tests so that the behavior in further language evolution won't break this contract.

What do you think?

1 Like

However, for the use case described by the original poster, nested classes are unlikely.

I actually want to use nested classes very much, but you can't use them with Interface Builder so I don't. I just name things with underscores instead of dots. :persevere:

As for the API contract, I'm interested to know what people use the description of self available in default parameters for. If it's useless, and gets changed, I wonder if it would solve the forwarding problem.

What do you think?

Honestly, I’m not sure how I feel about this. The one thing I will say is that this is most definitely an ‘evolution’ thing, and that’s outside of my remit.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple