While trying to implement a function in Swift I ran into an issue where it seems that type inference broke on certain function parameters. Taking notes from other languages, I wanted to implement a function in Swift which takes two parameters: an object and a block which returns another object of the same type as the previous object. I started implementing it like so:
func prepare<T>(_ object: T?, with block: ((T?) -> T?)) -> T? {
return block(object)
}
After attempting to use that inside of a UITableViewController
class, I experienced an issue with the naming, as it seemed to be struggling with the segue function prepare(for:sender:)
I decided to just change the name to resolve this one (I'll save the above for another question):
func bootstrap<T>(_ object: T?, with block: ((T?) -> T?)) -> T? {
return block(object)
}
This name will do for now. So I have a bootstrap(_:with:)
function which takes an object and a closure, returning an object of the same type as the object argument. I can call this easily with certain objects, for example an Int
:
bootstrap(1, with: { $0 + 1 }) // returns 2
This of course isn't what the function is there for, however. So I used it in its actual intended application, where I have a small UIView
-derived class with a single member:
class ContactDetailHeaderView: UIView, Nibbable {
var user: User?
}
This is fetched from a nib using the following protocol extension function on the Nibbable
protocol:
extension Nibbable where Self: UIResponder {
static func getRoot(withOwner owner: Any, options: [AnyHashable:Any]? = nil) -> Self? {
return self.nib.instantiate(withOwner: owner, options: options).first as? Self
}
}
With this, we can retrieve an object from nib
(our UINib
object for this class) and cast it as Self
which in this case is ContactDetailHeaderView
. Then, we can use our previously declared function to setup the object. We do this inside of a UITableViewController
-derived class ContactDetailTableViewController
which I will condense for the purpose of this question:
class ContactDetailTableViewController: UITableViewController {
var user: User?
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.backgroundView = bootstrap(ContactDetailHeaderView.getRoot(withOwner: self), with: { $0?.user = self.user })
}
}
However, we get an error invoking this function:
Value of type 'UIView' has no member 'user'
This is unexpected. Our getRoot(withOwner:options:)
function should return Self
which is ContactDetailHeaderView
, right?
Let's expand on the type inference by replacing the shorthand arguments with named parameters and explicitly defined types:
bootstrap(ContactDetailHeaderView.getRoot(withOwner: self),
with: { (header: ContactDetailHeaderView?) -> ContactDetailHeaderView? in
header?.user = self.user
}
)
Surely there is no way that our object can be interpreted as UIView
now? Well, we still get the same error:
Value of type 'UIView' has no member 'user'
Why does this happen? Even when I check the autocomplete in Xcode, the header
argument here shows the user
member as a valid accessible property. Using the getRoot(withOwner:options:)
function elsewhere produces the expected result:
ContactDetailHederView.getRoot(withOwner: self)?.user = self.user
Then why does it not work inside the bootstrap(_:with:)
closure? Is this a bug? Is there something else I'm missing?
Any help would be greatly appreciated.
I'll attach a gist with enough code to reproduce the problem in a playground. For simplicity's sake, I'll replace some of the unnecessary bits with something more manageable (e.g. change user
type from User
which is an internal object to String
which is available in the standard library)
Please keep in mind that this question is not about what the function does or why it does it. There are plenty of ways to implement the same thing, many of which are likely far better. I would like to solve this compiler error regardless of whether or not I even use this function in its current form.
Please keep in mind that this question is not about what the function does or why it does it. There are plenty of ways to implement the same thing, many of which are likely far better. I would like to solve this compiler error regardless of whether or not I even use this function in its current form.
Good, because this has way too many optionals. But it's easy to fix (it's just really hard to understand because it's too complicated).
bootstrap
is supposed to return a T?
. You pass it this:
{ (header: ContactDetailHeaderView?) -> ContactDetailHeaderView? in
header?.user = self.user
}
That returns Void
. That causes the type engine to run down all kinds of rabbit holes (especially since any T
can become T?
). The fix is to return T
:
{ (header: ContactDetailHeaderView?) -> ContactDetailHeaderView? in
header?.user = self.user
return header
}
In your gist, this would be:
self.tableView.backgroundView = bootstrap(ContactDetailHeaderView.getRoot(withOwner: self),
with: { $0?.user = self.user; return $0 })
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments