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 :
A link to the source code of the application is provided at the end of the post.
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.
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.
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
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.
- 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.
The application
The following screenshots illustrates an application which gets the current foreign exchange rates for approximately 66 currencies from Time Geniewebsite (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 Databutton 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,
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,
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 Databutton 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
- Time Genie 2011, Foreign Exchange Rates (FOREX), viewed 15 October 2011, <http://rss.timegenie.com/forex.txt>.
- Cocoa 2011, Mac OS X, Apple Inc, viewed 15 October 2011, <http://developer.apple.com/technologies/mac/cocoa.html>.