0

I run into an interesting behaviour which I don't understand. Here is code that produces this behaviour:

import UIKit

protocol UIViewNibLoading {
    static var nibName: String { get }
}

extension UIView : UIViewNibLoading {

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

}

extension UIViewNibLoading where Self : UIView {

    static func loadFromNib<T: UIViewNibLoading>() -> T {
        print(T.nibName)
        print(nibName)
        return UINib(nibName: nibName, bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! T
        // CRASH: return UINib(nibName: T.nibName, bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! T
    }

}

Here is output from console when this code is executed:

UIView
MyCustomViewSubclass

When I call then loadFromNib method on my custom class. It produces two different behaviours depending on how do I get the nibName.

  1. T.nibName: This returns string UIView
  2. nibName: This returns string MyCustomViewSubclass

Do you know what is going on here? Why self and T is not the same object during the runtime? Here is another one interesting thing I've found out. Here is what you can see in debugger when you put breakpoint into nibName getter:

T.nibName: T.nibName nibName: nibName

This is called as:

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    if section == WidgetAddTableViewController.SectionIndexRecent {
        return WidgetAddHeaderView.loadFromNib()
    } else if section == WidgetAddTableViewController.SectionIndexFreeAndPremium {
        return WidgetAddFilterHeaderView.loadFromNib()
    }
    return nil
}

Thanks for any explanation.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
Vojta
  • 810
  • 10
  • 16
  • How do you call `loadFromNib`? `T` will be decided at that point, not at this point. – Rob Napier May 24 '18 at 18:43
  • I call this `MyCustomClass.loadFromNib()` in my controller in viewForHeaderInSection method. At that point it produces described behaviour. – Vojta May 24 '18 at 18:50
  • Right but what do you assign that to? That's the important piece from your code. I'm betting it's something like `let view: UIView = MyCustomClass.loadFromNib()`? – Rob Napier May 24 '18 at 18:51
  • I return it in that delegate method – Vojta May 24 '18 at 18:51
  • func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return WidgetAddHeaderView.loadFromNib() } – Vojta May 24 '18 at 18:52
  • @RobNapier: do you think that if I would do `let header = WidgetAddHeaderView.loadFromNib()` than it wouldn't produce string `UIView` but `WidgetAddHeaderView`? In my case it mean crash of application because it's looking for nonexisting xib. – Vojta May 24 '18 at 18:56
  • That probably won't compile since it won't be able to figure out `T`. – Rob Napier May 24 '18 at 19:00

1 Answers1

1

self is resolved at runtime. T is resolved at compile-time. So at compile time, your code behaves like this:

let returnValue: UIView? = WidgetAddHeaderView.loadFromNib()
return returnValue

loadFromNib is generic over its return type. Given this code, the only valid return type is UIView. Again, this is decided at compile-time.

self on the other hand, is just a variable. It's a ever-so-slightly special-cased variable, but it's really just a variable. It has a run-time value. So type(of: self) is evaluated at run-time. And dynamic dispatch is handled at run-time.

The mistake is that you don't really mean to return "some unknown T that conforms to UIViewNibLoading" (which is what you say you return by making the return type generic). What you mean to return is Self, the class that the static function is a member of (determined at compile time). So you say so:

extension UIViewNibLoading where Self : UIView {

    static func loadFromNib() -> Self {
        print(nibName)
        return UINib(nibName: nibName, bundle: nil)
            .instantiate(withOwner: nil, options: nil)[0] as! Self
    }   
}

Or you could promise less (since your caller doesn't actually care) and do this:

extension UIViewNibLoading where Self : UIView {

    static func loadFromNib() -> UIView {
        print(nibName)
        return UINib(nibName: nibName, bundle: nil)
            .instantiate(withOwner: nil, options: nil)[0]
    }
}

But there's no reason to make this method generic, and it in fact hurts you as you've seen.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I'm going to use this method across the whole application, so the caller will care about the type of returned object. Just in that case of `viewForHeaderInSection` I don't care about object's type. I don't want to typecast it all the time, so return type is important. – Vojta May 24 '18 at 19:02
  • What `Self` means in that implementation? I tried to put there `T` instead, but compiler didn't allow it. – Vojta May 24 '18 at 19:02
  • OK; then you mean `Self`. – Rob Napier May 24 '18 at 19:02
  • `Self` is the type that the static member was called on, determined at compile-time. So in `UIView.f()`, `Self` is `UIView`. In `UIButton.f()`, `Self` becomes `UIButton`. But it's determined at compile-time. – Rob Napier May 24 '18 at 19:04
  • What `T` meant in my original implementation? I thought that it's the type on which was static function called, but you just told me that it was `Self`. – Vojta May 24 '18 at 19:13
  • No, `T` is a type parameter passed by the caller (like a normal value parameter, but passed at compile time; you calling it `T` is not special, that's just the name you gave it). In your case, since it's the return value, it is inferred to be the type of the thing the caller assigns it to. You're assigning it to something of type `UIView`, so `T == UIView` because that's what the caller says it is. Again, `T` is just the name of the type parameter you chose to create. "T" doesn't mean anything more than "nibName". – Rob Napier May 24 '18 at 19:16
  • Your function signature does nothing to promise that `T` is anything other than some type that conforms to `UIViewNibLoading`. It is not at all promised to be `Self`. – Rob Napier May 24 '18 at 19:18
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/171724/discussion-between-vojta-and-rob-napier). – Vojta May 24 '18 at 19:22