Saturday, December 31, 2011

MVC Pattern

Separate Your Concerns II
This report is aimed at discussing a table view application that sources data from the Internet. The application should have the following features :
  • The application should separate data model, control logic, and display code following the MVC pattern. It is acceptable to control views from your controllers (e.g. set the background color of the view); it is not acceptable to implement how the views are drawn (e.g. implementing drawing commands that actually color the background).
  • The user should be able to refresh, which makes a call to the Internet, updates the application’s data, and then updates the table view.
  • Application must be able to work offline.
Good object-oriented design will help code be resilient to changes, new features, and portability. Apple uses the Model-View-Controller design pattern as part of the UIKit framework on which most iOS apps are built. In the following, how Model-View-Controller design pattern is used to implement class structure and communication between classes is explained. In addition, how this design will resiliently handle possible changes is also explained.

The application
The following screenshots illustrates an application which gets the current foreign exchange rates for approximately 66 currencies from Time Genie website (URL is provided in references section). The base rate is the Euro and so the Euro's value will always be 1. The application saves the data in a comma-separated format file and presents it in a UITableView to users. The file containing the data can be synchronized with its source (Time Genie website) using the Refresh Data button provided (Data is updated on daily basis by the Time Genie website).

A link to the source code of the application is provided at the end of the post.

Image 1: Application in 3 states (from left to right, before refreshing, refreshing and after refreshing)

Note that in the image above, before refreshing the data, the first 7 items in the file was manipulated manually to show the effect of the refresh button.

MVC pattern in exchange rates viewer application
The exchange rates viewer application presents currency rates for approximately 66 countries, based on Euro, in a table view format. The data is sourced from a website in comma-separated text format. The application saves a copy of the file the first time it connects to the source website; thereafter, the data is loaded from the file. This functionality and all other relevant operations are handled in the model layer of the application, which is a class called DataProcurer and is a subclass of NSObject.

Model layer checks the existence of the data file in its init method using an NSFileManager. In case the file does not exist, information is read from the source website and is saved to a file in application directory so that it can be used by the application for the next times. It provides the model with the ability to produce data even if there is no connection to the internet. Model layer has an instance variable of type NSMutableArray called currencyRates using which information is communicated to other layers. Instance variable currencyRates can be accessed from outside of the model layer via its accessor methods (accessor methods are generated using @synthesize notation).

There are other methods in model layer that are used to manipulate data. Method refreshData simply deletes the data file using a NSFileManager and tries to repopulate the instance variable currencyRates using the same method that is called in init method. Therefore, because the file does not exist anymore, the updated version will be provided from the source website. The source code for important methods of the DataProcurer class is provided in the following.

-(id)init
{
    self = [super init];
    if (self != nil)
    {  
        currencyRates = [[NSMutableArray alloc] init];
        currencyRates = [self giveMeData];
    }

    return self;
}

-(void) refreshData
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(
                                         NSDocumentDirectory,
                                         NSUserDomainMask, YES);
    NSString *docDirPath = [paths objectAtIndex:0];
    NSString *filePath = [docDirPath
                  stringByAppendingPathComponent:@"mydata.txt"];
      
    NSFileManager* fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:filePath])
    {
        [fileManager removeItemAtPath:filePath error:nil];      
    }   
    currencyRates = [self giveMeData];
}

- (NSMutableArray*)giveMeData
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(
                                       NSDocumentDirectory,
                                       NSUserDomainMask, YES);
    NSString *docDirPath = [paths objectAtIndex:0];
    NSString *filePath = [docDirPath
                     stringByAppendingPathComponent:@"mydata.txt"];
    NSURL* url;
    NSString* content;   
    NSFileManager* fileManager = [NSFileManager defaultManager];
   
    if ([fileManager fileExistsAtPath:filePath])
    {
        content = [[NSString alloc]
                   initWithContentsOfFile:filePath
                   encoding:NSUTF8StringEncoding error:nil];      
    }
    else
    {
        url = [[NSURL alloc]
              initWithString:@"http://rss.timegenie.com/forex.txt"];
        content = [[NSString alloc]
                   initWithContentsOfURL:url
                   encoding:NSUTF8StringEncoding error:nil];
        [url release];
        [content writeToFile:filePath atomically:YES
                    encoding:NSUTF8StringEncoding error:nil];
    }
   
    NSMutableArray* tmpMutableArray = [[NSMutableArray alloc]
                               initWithArray:[content
                               componentsSeparatedByString:@"\n"]];
    [content release];
   
    NSMutableString* currency = [[NSMutableString alloc] init];
    NSMutableString* rate = [[NSMutableString alloc] init];
    NSMutableArray* mutableArray = [[NSMutableArray alloc] init];
    for (int i=0; i < [tmpMutableArray count]; i++)
    {
        NSMutableString* tmp = [[NSMutableString alloc] init];
        [tmp setString:[tmpMutableArray objectAtIndex:i] ];
       
        int l = [tmp length];
        if (l > 0)
        {
            [currency setString: [[tmp
                                  componentsSeparatedByString:@"|"]
                                 objectAtIndex:0] ];
            [rate setString: [[tmp
                               componentsSeparatedByString:@"|"]
                             objectAtIndex:2] ];
       
            [tmp setString:@"Every 1 EURO is "];
            [tmp appendString:rate];
            [tmp appendString:@" "];
            [tmp appendString:currency];
       
            [mutableArray addObject:tmp];
        }
       
        [tmp release];
    }
   
    [tmpMutableArray release];
    [currency release];
    [rate release];
   
    NSMutableArray* r = [[NSMutableArray alloc]
                         initWithArray:mutableArray];
    [mutableArray release];
   
    return r;
}

The controller layer, Assignment6ViewController class which is a subclass of UIViewController, mediates between model and view layers. To do so, it has an instance variable of type DataProcurer, the model layer, and an IBOutlet of type UITableView that is associated with the table view in the view layer. In the viewDidLoad method of the controller class, the instance variable of type DataProcurer is set to act as the datasource of the table view. It should be mentioned that DataProcurer class adopts the UITableViewDataSource protocol.

Touching Refresh Data button triggers an IBAction method in the controller class. In this method all that has to be done is to send a refreshData message to the model layer so that information can be sourced from the internet again. Consequently, the table data will need to be reloaded which is done after calling refreshData in the IBAction method. The source code for important parts of the Assignment6ViewController class is provided in the following:

- (void)viewDidLoad
{
    [super viewDidLoad];
    dataProcurer = [[DataProcurer alloc] init];
    tableView.dataSource = dataProcurer;
}

-(IBAction) btnRefreshDataPressed:(id)sender
{
    [self.view addSubview:modalView];
   
    [self performSelectorInBackground:@selector(background)
                                           withObject:nil];
}

-(void)background
{
    NSAutoreleasePool *p=[[NSAutoreleasePool alloc] init];
    [dataProcurer refreshData];  
    [tableView reloadData];
   
    [self performSelectorOnMainThread:@selector(backgroundDone)
                                     withObject:nil
                                      waitUntilDone:NO];
    [p release];
}

-(void)backgroundDone
{
    [self.modalView removeFromSuperview];
}
@end

The view layer, generated by Interface Builder, uses a table view to show the data. As discussed in previous sections, the view layer gets its required data from the model layer via an intermediary object that is the view controller. Using Interface Builder, no code is written and everything is exclusively focused on the View of the application (Cocoa 2011).

There are some points at which exchange rates viewer application could be improved. The application is currently storing its data in a flat file in a comma-separated format. It would be better if information was stored in NSManagedObjects using core data. In that case, firstly, information could be sourced incrementally if needed. Whereas now, the data file is deleted and regenerated every time an update is necessary. Secondly, the data model could be expanded to keep the date of the information so that it could be checked whether or not the data needs to be updated when the application runs. This check could happen automatically in model layer or instead, model layer could provide a method for controllers so that they can ask the model to do so.

Switching the model layer from using flat files to core data will be simple in this application. All the changes will be limited to the model layer and the way in which the data is stored. Communication of information between model and view controller will remain unchanged. The view controller does not know and does not need to know how information is stored. All it needs is an array of strings to use as the data source of the table view IBOutlet that it is keeping; no matter how the array is generated or where it is sourced from.

Diagram




References

No comments:

Post a Comment