📜 ⬆️ ⬇️

MVC on iPhone: “The Model” (Part 1)

From the very beginning, CocoaTouch was created with an eye to the MVC paradigm. Almost all templates, views and their controllers for the user are ready. The key classes are " UIView " and " UIViewController ". In many cases, the " UIView " method is applicable by itself - with the addition of user interface elements to the common " UIView " in the IB editor. To create your own functions, add subclasses to " UIViewController ". The " IBOutlet " specifiers allow you to associate user interface elements with the view, providing access to them.

And what about the concept of "Model" ? I practically did not find any information about him. In programming lessons with the model they prefer not to work by dialing the code directly in the controllers.

Having achieved, as it seemed to me, quite good results with implementation, I offer them here for discussion and evaluation. I will outline in brief. I create a class " Singleton " that extends the " NSObject " for my model. Then, by monitoring the keys / variables, I find out about updates. This is a lot like ModelLocator from Cairngorm , if someone had to work with him in Flex .
')
First, create a project with a couple of views. One of them will allow the user to change the value. This value is set for the model, which, in turn, triggers the change in another view. For this purpose, the " Utility Application " template is quite suitable. So, we create a project on its basis and assign the name " MVC " to it. (In principle, the name can be any, but for the convenience of working with a lesson it is better to duplicate it.)

The result should be something like this:

mvc_01

As you can see, we have the objects " Main View " and " Flipside View ", and for both there are controllers and nib-files . Run the project and preview the code. I will focus mainly on the points concerning the model, assuming that the rest is clear.

Now add a label and a text field to the views. The label is for " Main View ", the field, respectively, for " Flipside View ". First, let's deal with outlet type variables.

The file " MainViewController.h " should look like the one shown below.

#import

@ interface MainViewController : UIViewController {
UILabel *label;
}

@property (nonatomic, retain) IBOutlet UILabel *label;
@end
[/cc]

"<strong>MainViewController.m</strong>" — :

[cc lang= "objc" ]
#import "MainViewController.h"
#import "MainView.h"

@implementation MainViewController
@synthesize label;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
//
}
return self;
}

/*
// viewDidLoad , , nib-.
- (void)viewDidLoad {
[super viewDidLoad];
}
*/

/*
// , .
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/

- ( void )didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // , superview
// ,
}

- ( void )dealloc {
[label release];
[super dealloc];
}

@end


* This source code was highlighted with Source Code Highlighter .


Similarly, in the file " FlipsideViewController.h " we add the variable " textField ", as well as " IBAction ", which will let us know about changing the text in the field

#import

@ interface FlipsideViewController : UIViewController {
UITextField *textField;
}

@property (nonatomic, retain) IBOutlet UITextField *textField;

- (IBAction)textChanged:(id)sender;
@end


* This source code was highlighted with Source Code Highlighter .


and FlipsideViewController.m:

#import "FlipsideViewController.h"

@implementation FlipsideViewController
@synthesize textField;

- ( void )viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor];
}

/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/

- ( void )didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
}

- (IBAction)textChanged:(id)sender
{

}

- ( void )dealloc {
[textField release];
[super dealloc];
}

@end


* This source code was highlighted with Source Code Highlighter .


Now you can open " MainView.xib " and add " UILabel " to the view. Drag a connecting line from the File's Owner object (class " MainViewController ") to the label, associating it with the corresponding variable.

mvc_02mvc_03

Do the same in the " FlipsideView.xib " file by adding the " UITextField " element and linking it to the " textField " variable for " File's Owner " (" FlipsideViewController "). Connect the IBAction " textChanged " to the text box's " editingChanged " event.

mvc_04mvc_05

By this time, probably, it became clear that our goal is to enter some text into the field, which then appears on the label. But are these two separate views with their controllers? How to improve the interaction between them? Using a model, of course. After saving both nib files , close the IB editor and return to Xcode .

Model.


It's time to create a model. Add a new file to the project - a subclass for " NSObject ". Give the class the name " Model ". It’s not that I’m an ardent fan of Singleton patterns. I am well aware of their shortcomings and I believe that sometimes they are used unnecessarily, but as a pragmatist, I am sure that in this case it is an acceptable option.

Those who are intolerable with the very idea of ​​singletons can create a model in the " RootViewController " and pass on an instance to each of the view controllers. That should work. I will choose another way, using the ingenious " SynthesizeSingleton " file from [ CocoaWithLove.com ] . By the way, there you can express your attitude to singletons.

Now add the file " SynthesizeSingleton.h " to the project and call the macro " SYNTHESIZE_SINGLETON_FOR_CLASS " in the implementation of the class that you want to make a singleton.

I noticed that working with this macro always triggers a warning, unless you declare the static method " sharedModel " in the interface file.

Our model will also need one single property, text, and custom accessors. Here is the " Model.h " file:

#import <Foundation/Foundation.h>

@ interface Model : NSObject {
NSString *text;
}

@property (nonatomic, retain) NSString *text;

+ (Model *)sharedModel;
@end


* This source code was highlighted with Source Code Highlighter .


And here is " Model.m ":

#import "Model.h"
#import "SynthesizeSingleton.h"

@implementation Model

SYNTHESIZE_SINGLETON_FOR_CLASS(Model);

@synthesize text;

- (id) init
{
self = [super init];
if (self != nil) {
text = @"" ;
}
return self;
}

- ( void ) dealloc
{
[text release];
[super dealloc];
}

@end


* This source code was highlighted with Source Code Highlighter .


Configure data for the model.


If you change the text in the text field, the " textChanged " method will be called in the " FlipsideViewController " controller. Here you can assign a new value to the text property of the model.

- (IBAction)textChanged:(id)sender
{
Model *model = [Model sharedModel];
model.text = textField.text;
}


* This source code was highlighted with Source Code Highlighter .


Be sure to import " Model.h " into " FlipsideViewController.m ".

Commit changes.


The main view is enough to tell when the model will change. This can be done by observing the key / value. Accordingly, you need to set the observer to a specific property of the object so that it immediately calls the method if it changes. Here we need to know when the text property changes for the class of the singleton model. For this purpose, we will use the " initWithNibName " method of the " MainViewController " class. Here is what it looks like:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
//
Model *model = [Model sharedModel];
[model addObserver:self forKeyPath: @"text" options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}


* This source code was highlighted with Source Code Highlighter .


First we get a reference to the singleton " Model ", after which we call the method " addObserver: forKeyPath: options: context ". The browser is " self ", the class " MainViewController". "KeyPath " is a property that will be monitored, a text string of characters. This options allows us to specify exactly which data will change. Here is the new value for the property. If desired, you can query the initial, outdated or previous value, as well as any combination of them. Zero can be added as context.

The change followed by the browser should implement a special method that will be called only in this case. During its signature:

- (void) observeValueForKeyPath: (NSString *) keyPath ofObject: (id) object change: (NSDictionary *) change context: (void *) context

" keyPath " is the name of the property as a string, " object " is the object that owns this property (in this case, the model), " change " is the dictionary that stores the specified old, new, previous and initial values, " contex t" - any case-relevant context (we have zero).

If the goal is to observe several properties of a model at once, a select statement with " keyPath " will be required, indicating which property has changed. For now, we are just reviewing the text, so we already know it. You can get a new property value by sending a request to change the dictionary parameters. Another option is to directly access the text property of the model, but we will use the data below:

- ( void )observeValueForKeyPath:(NSString *)keyPath ofObject:(id) object change:(NSDictionary *)change context:( void *)context
{
label.text = [change valueForKey: @"new" ];
}


* This source code was highlighted with Source Code Highlighter .


Here we ask the object to change its “new” value (which will be all its contents, since this is the option we have given). We assign the result " label.text " - and everything is ready.

Well that's all. We have a model that stores data and reports events as they change. Note: in the Cocoa MVC paradigm, views are usually not directly followed by the model. This is what view controllers do, and they also tell views what to do. Such an approach does not have the status of a law, but is generally accepted.

I wanted to deal with another concept - using " NSNotification " instead of monitoring the key / value. I'm not sure that this is really a worthy alternative, and what are its pros and cons.

The above example has no practical value - it is just an illustration. In a real application, much could be done differently, for example, not to fix every editing event, going to it only after closing the " Flipside " view. But the goal of our lesson was not at all in this, but in the visual introduction of the model (at least in ONE of the ways).

Thank you and success!

The source code for the lesson can be downloaded here .

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


All Articles