I'm a swift beginner, so be gentle...
I'm having trouble assigning a function as a parameter.
I have defined this struct:
struct dispatchItem {
let description: String
let f: ()->Void
init(description: String, f: @escaping ()->()) {
self.description = description
self.f = f
}
}
I make use of this within a class called MasterDispatchController
like so:
class MasterDispatchController: UITableViewController {
let dispatchItems = [
dispatchItem(description: "Static Table", f: testStaticTable),
dispatchItem(description: "Editable Table", f: testEditableTable)
]
func testEditableTable() {
//some code
}
func testStaticTable() {
//some code
}
etc.
Then I have a table view in my code that dispatches out to whichever function was clicked on (there are more than just the two I showed in the code above, but that's unimportant) like so
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].f()
}
So... The compiler is not happy with this. It says when I am defining the dispatchItems let statement:
Cannot convert value of type '(MasterDispatchController) -> () -> ()' to expected argument type '() -> ()'
I figure... ok... I'm not sure I exactly understand this, but it seems like the compiler wants to know what class the callback functions are going to come from. I can see why it might need that. So I kind of blindly follow the pattern the compiler gives me, and change my struct to:
struct dispatchItem {
let description: String
let f: (MasterDispatchController)->()->Void
init(description: String, f: @escaping (MasterDispatchController)->()->()) {
self.description = description
self.f = f
}
}
Great the compiler is happy there, but now when I try to call the function with dispatchItems[indexPath.row].f()
it says:
Missing parameter #1 in call
The function has no parameters, so I got confused...
I thought maybe it was asking me for the instance of the object in question which would make some sense... that would be "self" in my example, so I tried dispatchItems[indexPath.row].f(self)
but then I got an error:
Expression resolves to an unused function
So I'm kind of stuck.
Sorry if this is a stupid question. Thanks for your help.
The problem is that you're trying to refer to the instance methods testStaticTable
and testEditableTable
in your instance property's initialiser before self
is fully initialised. Therefore the compiler cannot partially apply these methods with self
as the implicit parameter, but can instead only offer you the curried versions – of type (MasterDispatchController) -> () -> ()
.
One might be tempted then to mark the dispatchItems
property as lazy
, so that the property initialiser runs on the first access of the property, when self
is fully initialised.
class MasterDispatchController : UITableViewController {
lazy private(set) var dispatchItems: [DispatchItem] = [
DispatchItem(description: "Static Table", f: self.testStaticTable),
DispatchItem(description: "Editable Table", f: self.testEditableTable)
]
// ...
}
(Note that I renamed your struct to conform to Swift naming conventions)
This now compiles, as you now can refer to the partially applied versions of the methods (i.e of type () -> Void
), and can call them as:
dispatchItems[indexPath.row].f()
However, you now have a retain cycle, because you're storing closures on self
which are strongly capturing self
. This is because when used as a value, self.someInstanceMethod
resolves to a partially applied closure that strongly captures self
.
One solution to this, which you were already close to achieving, is to instead work with the curried versions of the methods – which don't strongly capture self
, but instead have to be applied with a given instance to operate on.
struct DispatchItem<Target> {
let description: String
let f: (Target) -> () -> Void
init(description: String, f: @escaping (Target) -> () -> Void) {
self.description = description
self.f = f
}
}
class MasterDispatchController : UITableViewController {
let dispatchItems = [
DispatchItem(description: "Static Table", f: testStaticTable),
DispatchItem(description: "Editable Table", f: testEditableTable)
]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].f(self)()
}
func testEditableTable() {}
func testStaticTable() {}
}
These functions now take a given instance of MasterDispatchController
as a parameter, and give you back the correct instance method to call for that given instance. Therefore, you need to first apply them with self
, by saying f(self)
in order to get the instance method to call, and then call the resultant function with ()
.
Although it may be inconvenient constantly applying these functions with self
(or you may not even have access to self
). A more general solution would be to store self
as a weak
property on DispatchItem
, along with the curried function – then you can apply it 'on-demand':
struct DispatchItem<Target : AnyObject> {
let description: String
private let _action: (Target) -> () -> Void
weak var target: Target?
init(description: String, target: Target, action: @escaping (Target) -> () -> Void) {
self.description = description
self._action = action
}
func action() {
// if we still have a reference to the target (it hasn't been deallocated),
// get the reference, and pass it into _action, giving us the instance
// method to call, which we then do with ().
if let target = target {
_action(target)()
}
}
}
class MasterDispatchController : UITableViewController {
// note that we've made the property lazy again so we can access 'self' when
// the property is first accessed, after it has been fully initialised.
lazy private(set) var dispatchItems: [DispatchItem<MasterDispatchController>] = [
DispatchItem(description: "Static Table", target: self, action: testStaticTable),
DispatchItem(description: "Editable Table", target: self, action: testEditableTable)
]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dispatchItems[indexPath.row].action()
}
func testEditableTable() {}
func testStaticTable() {}
}
This ensures that you have no retain cycles, as DispatchItem
doesn't have a strong reference to self
.
Of course, you may be able to use unowned
references to self
, such as shown in this Q&A. However, you should only do so if you can guarantee that your DispatchItem
instances don't outlive self
(you would want to make dispatchItems
a private
property for one).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With