⬆️ ⬇️

Context capture by closures instead of delegation in iOS 8 Swift





When designing iOS applications with many MVCs, it is necessary to solve information transfer issues from one MVC to another, both in the forward and in the opposite direction. The transfer of information in the forward direction during the transition from one MVC to the next is usually done by installing the MVC Model where we go, but the transfer of information "back" from the current MVC to the previous one is done through delegation in both Objective-C and Swift.



In addition, delegation is used within the same MVC between the View and the Controller for their “blind interaction.”

')

The fact is that Views are too generic standardized building blocks, they cannot know anything about the class or the Controller that uses them. Views cannot own their own data; data belongs to the Controller. In fact, the data may be in the Model, but the Controller is responsible for providing it. Then how can View communicate with the Controller? With the help of delegation .



You need to complete 6 steps to implement delegation in the interaction between View and Controller:



  1. We create a delegation protocol (we define what V iew wants C ontroller to take care of)
  2. Create a weak delegate property in V iew, the type of which will be the delegation protocol
  3. We use the delegate property in V iew to get data / do things that V iew cannot own or control
  4. C ontroller announces that it implements the protocol
  5. C ontroller establishes self (itself) as delegate V iew by setting the property in item # 2 above
  6. We implement the protocol in C ontroller


We see that delegation is not an easy process.

In both Swift and Objective-C, the delegation process can be replaced by using closures (blocks), given their ability to capture any variables from the surrounding context for internal use. However, in Swift, the implementation of this idea is significantly simplified and looks more concise, since functions (closures) in Swift are “first-class citizens”, that is, they can be declared variables and passed as function parameters. The simplicity and absolute clarity of the code in Swift will allow more extensive use of closures that capture context for the interaction of two MVCs or the interaction of a Controller and View without the use of delegation .



I want to show the use of capturing context with closures on two examples taken from the Stanford course 2015 “Developing iOS 8 Apps with Swift” (the Russian equivalent is on the site “Developing iOS + Swift + Objective-C applications ” ).



One example will concern the interaction of View and Controller within one MVC , and the other two different MVCs . In both cases, capturing the context with closures will allow us to replace delegation with simpler and more elegant code that does not require supporting protocols and delegates.



In the Tasks of the Stanford course it is proposed to develop a graphing calculator,







which on the iPad looks like it consists of two parts: on the left is the RPN ( reverse Polish notation ) calculator, which allows not only to perform calculations, but using the variable M , set the expression for the function, which, when you click the Graph button, is graphically reproduced in the right parts of the screen. These expressions can be memorized in the list of functions by clicking the " Add to Favorites " button and playing back the entire list of memorized functions using the " Show Favorites " button. In the list you can choose any function (picture in the title), and it will be built in the graphic part. Having a set of some functions, you can produce them graphically, without resorting to the RPN calculator.

In addition, you can delete an unnecessary function from the list using the Swipe gesture (swipe) from right to left.







I will not dwell on the implementation of the RPN calculator, the process of building it is described on the website "Developing iOS + Swift + Objective-C applications . " We will be interested in the graphical part, and in particular, how a user UIView receives information about the coordinate y = f (x) from its C ontroller, and how the standard Table View , appearing in the Popover window, causes the C ontroller of another MVC to draw the desired graph and maintain synchronous list of functions.

All MVC participating in the Graphing Calculator application are presented below.







We see that the Split View Controller is used, in which the role of the Master side is played by a calculator capable of generating functional dependencies of the type y = f (x) , and the role of the Detail is played by the Graph representing the dependence y = f (x) . We will be interested in the Detail side of the Split View Controller, namely the MVC “Graph” , on which we will work out the interaction of View and Controller within one MVC, and the MVC “List of functions” , on which we will work out its interaction with MVC “Graph” .



Capturing the context with the closure when the View and Controller interact in the same MVC.



Let's look at MVC “Graph ”, which is controlled by the FavoritesGraphViewController class.







Upon closer inspection, we find that the FavoritesGraphViewController class inherits from the GraphViewController base class and contains only what is associated with the list of functions represented by the favoritePrograms variable, which is an array of programs for the RPN calculator. The entire graphical part is hidden in the GraphViewController base class. From the point of view of the task set in the article, we are interested in exactly the GraphViewController base class, and we will return to the FavoritesGraphViewController class in the next section. This is a general trick in iOS programming, where a more generalized class remains intact, and all “particulars” are added to its subclass . In this section, we can assume that the scheme of our user interface has a more simplified form:







That is, the MVC “Graph” is controlled by the GraphViewController class, to which the program RPN calculator program is transferred for plotting (this is the MVC “Graph” model).







A view of this MVC is a regular UIView , managed by the GraphView class.







Our task is to create an absolutely generalized GraphView class that can build dependencies y = f (x) . This class should not know anything about the calculator, it should receive information about the chart as a general relationship y = f (x) and not store any data. On the other hand, our Controller, represented by the GraphViewController class, contains information about the graph y = f (x) , but not in an explicit form, but as a program that can be interpreted by an instance of the brain RPN calculator.







Having an arbitrary x value, you can calculate y using the brain calculator for the installed program







How to connect these two classes - GraphView and GraphViewController , when one of them has information that the other needs? The traditional and universal way to do this in both Objective-C and Swift is delegation . This method for this particular Swift example is described in the post “Task 3. Decision-Mandatory Tasks” .



We chose another way - using closures, which captures variables from the external context, for the interaction of two classes, in our case GraphView and GraphViewController .



Add the yForX closure variable as public (not private) to the GrapherView class so that it can be set to GrapherViewController







Using the Optional variable yForX , let's draw a graph in the GrapView class:







Note that in order to specify the Optionals chain, in the case when the function itself is Optional , the function needs to be placed in parentheses, put a mark ? questions and then write her arguments.

The GraphViewController in the Observer didSet {} Properties GraphView! which is @IBOutlet , we set the yForX closure so that it captures the link to the instance of my self.brain calculator, in which the necessary program for plotting the program is already installed. Each time you access yForX , the same “captured” calculator will be used, and this is what we need.







Everything. No delegates, no protocols, no confirmation of the protocols. The only thing we add to the so-called “ unowned self ” list is to exclude circular references in memory (this is described in Lecture 9 of the course “Developing iOS 8 Apps with Swift” ).



Code on github .



Capture context by closure when two MVCs interact.



Let us return to the option of the Graphic calculator, which can save the functions of graphs in a special list and prompt the user to select functions from the list for a graphic representation.







As mentioned above, for this we had to create a subclass of the class GraphViewController , which we called FavoritesGraphViewController . And now the MVC “Graph” is controlled by the FavoritesGraphViewController class.

In this new FavoritesGraphViewController class for the list of programs, we will place the favoritePrograms calculated variable, which is an array of programs for the RPN calculator and associated with the NSUserDefaults persistent storage. Add to the list of programs by using the " Add to Favorites " button. The current program is added to the favoritePrograms array.







To display the list of programs, use another MVC - MVC "List of Functions" . This is the usual Table View Controller , which is controlled by the FavoriteTableViewController class. “Moving” to MVC “List of Functions” is performed by pressing the “ Show Favorites ” button, which is located on MVC “Schedule” , using a segue of the type “Present as Popover”.



The model for the FavoriteTableViewController class is an array of programs for the RPN calculator that needs to be displayed in the table.







Execute the methods of the Table View DataSource





And immediately we are faced with the fact that we need to display in the row of the table not the program for the RPN calculator, but its description in a “civilized infix” form, because our MVC is called MVC “List of Functions” . For this you need to request a calculator, which is located in the MVC "Graph" .



Add to the class FavoriteTableViewController variable-closure descriptionProgram , the type of which is a function that has two parameters at the input:



The output is Optiona l string with a description:







We will set this closure in MVC "Graph" in the process of preparing for the "move" on MVC "List of functions" in the prepareForSegue method







The closure of the descriptionProgram will capture in the MVC "Graph" a calculator program and an array of programs and will use them with each call.



Let's return to our table and class FavoriteTableViewController . We need to ensure that the corresponding schedule is drawn when a particular function is selected in the table and to synchronize the deletion of a row in the list of functions with an array of programs located in the NSUserDefaults permanent storage. All this requires interaction with MVC "Graph" . Therefore, we add to the FavoriteTableViewController class two didSelect and didDelete variables-closures, the type of which is functions with the same signature, which, like the previous descriptionProgram -variable closure, has two parameters:



These functions do not return anything, since all actions are performed inside closures:







We will use the methods of the didSelectRowAtIndexPath delegate and commitEditingStyle ... and the just-declared closure variables to perform the tasks:







The didSelect and didDelete closures will be set in the MVC "Graph" in the process of preparing for the move to MVC "List of Functions" in the prepareForSegue method:







The didSelect closure captures the program in the MVC “Graph” program , which is installed for the calculator from the outside, and reinstalls it, which will force the MVC “Graph” to redraw the graph we need. In the same closure, you can remove the Popover window with the list of functions from the screen (just remove the comment from the line controller.dismissControlerAnimated ... ) or leave it for later selection by the user.



The didDelete closure captures the favoritePrograms program array associated with the NSUserDefaults persistent store and deletes the corresponding program.

So, we looked at how MVC "List of Functions" interacts with the MVC called "Graph" in the opposite direction using closures.



Now consider the direct interaction. Where is installed Model programs for MVC "List of functions" ? We will install it in the MVC "Graph" in the process of preparing for the move to MVC "List of functions" in the same method prepareForSegue







So, the use of closures for the exchange of information between different MVCs is very simple.

It consists of 3 steps:





Everything.

No supporting elements - protocols and delegates.



Code on github .



On the iPhone, using the Graphing Calculator is even more efficient, since it is not the Split View Controller that works there, but the Navigation Controller , and you are left alone with the list of functions on the screen.







Conclusion



We considered the transfer of information from one MVC to another MVC in both forward and reverse directions. The transfer of information in the forward direction during the transition from one MVC to the next, is carried out by installing the Model MVC where we are going. The transfer of information "back" from the current MVC to the previous MVC is very convenient and easy to implement in Swift using closures.



This technique can also be used within one MVC for “blind interaction” between the View and the Controller. A demo of the Graphing Calculator is presented, which shows all these features.



I draw your attention that the condition for the development of a Graphic calculator in Stanford courses was the creation of classes that support plotting and displaying a list of functions in a tabular form, as generic as possible, knowing nothing about the existence of the RPN calculator. Therefore, all variables - closures in all the examples presented have a very generalized ( generic ) form, associated exclusively with the semantics of the corresponding classes GraphView and FavoriteTableViewController .



Links



Stanford course 2015 "Developing iOS 8 Apps with Swift"

Russian unauthorized lecture notes and Tasks solutions are on the site “Developing iOS + Swift + Objective-C Applications ”

The text of Task 3 in English is available on iTunes under “Developing iOS 8 app: Programming: Project 3 ″ .

The text of Task 3 in Russian is available at Task 3 iOS 8.pdf.



Solution Task 3 "Graphing Calculator" from scratch.

Task 3 cs193p Winter 2015 Graphing Calculator. Solution - required items

Task 3 cs193p Winter 2015 Graphing Calculator. Solution - additional points 1, 2 and 3

Task 3. Solution - additional items 4, 5 and 6. Ending.

Code on github .

Note. If you experiment with the Graphing Calculator, then remember that this is a RPN calculator, so operands are entered first, and then the operation. To get the function sin (1 / M), you need to enter the following sequence of characters on the calculator

1 ↵ M ÷ sin “Graph” button gives sin (1 / M)

M cos M Ă— Graph button gives cos (M) * M

M ↵ 1 M sin + × button "Graph" gives M * (1 + sin (M))

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



All Articles