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
.
UIView
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:
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.
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.