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:
- We create a delegation protocol (we define what V iew wants C ontroller to take care of)
- Create a weak delegate property in V iew, the type of which will be the delegation protocol
- We use the delegate property in V iew to get data / do things that V iew cannot own or control
- C ontroller announces that it implements the protocol
- C ontroller establishes self (itself) as delegate V iew by setting the property in item # 2 above
- 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:
- FavoriteTableViewController - the class that requests this method
- index - the program index in the list of favoritePrograms programs that need an infix description
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:
- FavoriteTableViewController - the class that requests this method
- index - the program index in the list of favoritePrograms programs that need an infix description
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:
- In MVC, requiring interaction, create a public variable - closure
- Use it in the same MVC
- In another MVC, you set this closure either in the didSet {} Property Observer, or in the prepareForSegue method, or somewhere else, so that the closure “captures” the necessary variables and constants
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))