Search code examples
iosruntimeprotocolsswift-extensionsclass-extensions

self vs "generic typet" T difference when ceating extension


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.


Solution

  • 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.