Fast function as a parameter inside a class

I start fast, so be gentle ...

I am having trouble assigning a function as a parameter.

I defined this structure:

struct dispatchItem { let description: String let f: ()->Void init(description: String, f: @escaping ()->()) { self.description = description self.f = f } } 

I use this in a class called MasterDispatchController as follows:

 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 } 

and etc.

Then I have a table view in my code, which is sent depending on which function was pressed (there are more than just the two that I showed in the above code, but it doesn’t matter), 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 define dispatchItems let statement:

Cannot convert value of type '(MasterDispatchController) β†’ () β†’ ()' to the expected argument type '() β†’ ()'

I suppose ... well ... I'm not sure I understand that for sure, but it seems like the compiler wants to know from which class the callback functions will work. I understand why this may be necessary. Therefore, I kind of blindly follow the pattern that the compiler gives me, and change my structure to:

 struct dispatchItem { let description: String let f: (MasterDispatchController)->()->Void init(description: String, f: @escaping (MasterDispatchController)->()->()) { self.description = description self.f = f } } 

The excellent compiler is happy there, but now when I try to call the function using dispatchItems[indexPath.row].f() , it says:

Missing parameter # 1 when calling

The function has no parameters, so I got confused ...

I thought, maybe this asked me about the instance of the object in question, which will make sense ... it will be the "me" in my example, so I tried dispatchItems[indexPath.row].f(self) , but then I got the message about error:

Expression allows unused function

So I'm stuck.

Sorry if this is a stupid question. Thank you for your help.

+5
source share
3 answers

The problem is that you are trying to access the instance methods testStaticTable and testEditableTable in the instance property initializer before self is fully initialized. Therefore, the compiler cannot partially apply these methods using self as an implicit parameter, but instead can offer you curry versions - like (MasterDispatchController) -> () -> () instead.

Then it may be tempting to mark the dispatchItems property as lazy , so that the property initializer starts when the property is first accessed, when self fully initialized.

 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 structure according to Swift naming conventions)

Now this compiles, since now you can refer to partially applicable versions of methods (for example, type () -> Void ) and can call them like:

 dispatchItems[indexPath.row].f() 

However, you now have a hold loop, because you keep the closures on self , which grab self strongly. This is due to the fact that when using self.someInstanceMethod as a value, a partially closed close is allowed, which strongly fixes self .

One solution to this that you have come close to is to work with cardinal versions of methods that do not fix self strongly, but should instead be used with this instance to work 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() {} } 

Now these functions take the given instance of MasterDispatchController as a parameter and return the correct instance method to call this instance. So you need to apply them first with self , saying f(self) to call the instance method to call, and then call the resulting function with () .

Although it may be inconvenient to constantly apply these functions with self (or you may not even have access to self ). A more general solution would be to save self as a weak property on the DispatchItem along with the curry function - then you can apply it by request:

 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 do not have hold cycles, since DispatchItem does not have a strong reference to self .

Of course, you can use unowned self references, such as shown in this Q & A. However, you should only do this if you can guarantee that your DispatchItem instances do not expect self (you would like to make the dispatchItems a private property for one).

+2
source

The problem is that Swift treats class methods and functions differently under the hood. Class methods get a hidden self parameter (similar to how it works in Python), which allows them to know on which instance of the class they were called. Therefore, even if you declared testEditableCode as () -> () , the actual function is of type (MasterDispatchController) -> () -> () . He must know on which instance of the object he was called.

The right way to do what you are trying to do is to create a closure that calls the correct method, for example:

 class MasterDispatchController: UITableViewController { let dispatchItems = [ dispatchItem(description: "Static Table", f: {() in self.testStaticTable() }), dispatchItem(description: "Editable Table", f: {() in self.testEditableTable() }) ] func testEditableTable() { //some code } func testStaticTable() { //some code } 

If you are familiar with JavaScript, the notation {() in ...code...} is the same as function() { ...code... } or the same as lambda: ...code... in Python

+1
source

Here is a way to achieve what you want.

Implement the protocol with the necessary parameters:

 protocol Testable { func perfromAction() var description: String { get set } weak var viewController: YourViewController? { get set } //lets assume it is fine for testing } 

Access to your UIViewController like this is not entirely correct, but so far this is normal. You can access shortcuts, segues, etc.

Create a Class for each test you want:

 class TestA: Testable { var description: String weak var viewController: YourViewController? func perfromAction() { print(description) //do something viewController?.testCallback(description: description) //accessing your UIViewController } init(viewController: YourViewController, description: String) { self.viewController = viewController self.description = description } } class TestB: Testable { var description: String weak var viewController: YourViewController? func perfromAction() { print(description) //do something viewController?.testCallback(description: description) //accessing your UIViewController } init(viewController: YourViewController, description: String) { self.viewController = viewController self.description = description } } 

You can add some custom parameters for each Class , but these 3 are required from the protocol.

Then your UIViewController will look like

 class YourViewController: UIViewController { var arrayOfTest: [Testable] = [] override func viewDidLoad() { super.viewDidLoad() arrayOfTest.append(TestA(viewController: self, description: "testA")) arrayOfTest.append(TestB(viewController: self, description: "testB")) arrayOfTest[0].perfromAction() arrayOfTest[1].perfromAction() } func testCallback(description: String) { print("I am called from \(description)") } } 
0
source

Source: https://habr.com/ru/post/1266036/


All Articles