⬆️ ⬇️

Three20 demystifying: TTModel

Those who, because of the need to have to deal with Three20 , in the process of developing iOS applications, know that this library is more or less solving a certain range of tasks related to rapid prototyping, using global navigation, MVC approach to developing applications.



The main disadvantage of the library is that it lacks normal documentation, and one that is not enough for a normal understanding of what is happening inside this library.



This is not the only minus of the library, and there are many discussions on the Internet regarding the pros and cons of Three20.

')

It must be said that the main developer stopped development over Three20, and decided to create a new Nimbus library, the main idea of ​​which would be simplicity and high documentation:

“Nimbus is an iOS framework.”



Nevertheless, the project exists to this day, and some parts of this project deserve attention, and are sufficiently interesting from the point of view of design.



In this post will be discussed in detail issues related to the work of TTModel .



For those who are not going to use Three20 in their projects, it will be useful to look at one of the possible, noteworthy, implementations of the model in the MVC pattern.



Anyone else interested, please .



TTModel





Those who are still not familiar with the design pattern MVC (Model-View-Controller), or have forgotten what it is all about, can familiarize themselves with it on the Wiki . ( “Repetition is the mother of learning” after all)



Architecture





So, the TTModel in the Three20 library is a classic MVC model. That is, it is just an abstract data set that does not depend on the view or the controller.



The TTModel architecture initially implies that the model may be in different states:



In addition, TTModel provides several basic methods for working with it.





This architectural solution is fully justified, and allows you to solve the problem with hiding the implementation of the model. In fact, it doesn’t matter to us what the model is loaded with - whether it needs a query to the database to get up-to-date data, a query to the server, a call to 10 consecutive queries, or a complex computing task. It is important for us to know her condition. And as soon as the state has changed to isLoaded , we can take relevant data from this model.



//      DBRequestModel * someModel = [[DBRequestModel alloc] initWithQuery:@"SELECT * FROM TABLE USERS"]; //    [someModel load:TTURLRequestCachePolicyNone more:NO]; //    while ([someModel isLoading]) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } if ([someModel isLoaded]) { //    result DBRequestModel //   NSArray * users = [someModel result]; } else { //  ? } 


The above code is written solely to understand that the model does not immediately contain relevant data.



The code above could exist in some universe, but I would not advise using it in ours.



Tracking model state changes



Why should we follow the change in the state of the model? In order to know when it will be relevant data.




In order to monitor the state of the model, you must add an object (usually a controller) to the model delegates array.



 [[someModel delegates] addObject:self]; 




In turn, the model, when changing its state, will inform all its delegates about it through the TTModelDelegate protocol



The TTModelDelegate protocol provides a set of methods from which you need to select several basic ones:





So, by subscribing to the model changes, we implement the methods we need. All of the above methods are optional, and allow you to focus solely on the functionality that we need.



 - (void)showModelDataIfPossible { //      self.model = [[DBRequestModel alloc] initWithQuery:@"SELECT * FROM TABLE USERS"]; //        [[[self model] delegates] addObject:self]; // ,     //        if (! [[self model] isLoaded] && [[self model] isOutdated]) { //    [someModel load:TTURLRequestCachePolicyNone more:NO]; } else { //   ,           //     [self showModelData:[self model]]; } } #pragma mark - TTModelDelegate - (void)modelDidStartLoad:(id<TTModel>)model { //   .. //       [self showLoading]; } - (void)modelDidFinishLoad:(id<TTModel>)model { //      [self hideLoading]; //    //      [self showModelData:[self model]]; } - (void)model:(id<TTModel>)model didFailLoadWithError:(NSError*)error { //      [self hideLoading]; //      User-Friendly  TTAlertNoTitle(@"Error code #10"); //   User-Friendly  [self showReloadScreen]; self.reloadLabel.title = @"Something went wrong" "But you could try to reload data"; } - (void)modelDidCancelLoad:(id<TTModel>)model { //      [self hideLoading]; //      , //       //  ,       //    } - (void)showModelData:(id<TTModel>)model { //    result DBRequestModel //   NSArray * users = [someModel result]; //    [self showUsers:users]; } 




I hope it has become more or less clear what TTModel is and how to work with it. Now let's talk a little about delegates from the model.



delegates vs listeners



The delegates array in TTModel is a special array that does not retain objects when they are added. It is created using the TTCreateNonRetainingArray function.

This is done to avoid circular references, and to comply with the convention that the delegate should not be retaine'd .




In fact, the name for the property is not entirely correct. The delegate in its functionality should help the main object (in this case, the model) to solve the problem, and in the current implementation, the array of delegates is actually an array of listeners.



The main difference between the listener and the delegate is that the listener is informed about changes in the base object, and the delegate is most often asked for some data that is necessary for the functioning of the base object.



So, in fact, in TTModel an array of delegates based on functionality is an array of listeners.



TTURLRequestModel



For working with models that take data from the network, Three20 uses the TTURLRequestModel class. It is tied to TTURLRequest and provides a clear example of the fact that you can simply hide a specific implementation of the model.



TTURLRequestModel is essentially an abstract class, and doesn’t really know anything, except it adds a few additional fields to the model related to the network component.



What else is interesting in this model, due to the lack of normal documentation, it is not immediately clear how it works, and how to use it correctly :)



Create your model?





Suppose a model should provide us with a list of some entities, let's call them for simplicity by users.



The server provides data page by page. On each page there are N users.



Let's hit the road!



Declaring interface



 @interface ItemsListModel : TTURLRequestModel { /*      */ NSMutableArray * _items; /*  URL  */ NSString * _baseURLString; /*   */ int _page; /*     */ int _itemsOnPage; } @property(nonatomic, readonly) NSMutableArray * users; @property(nonatomic, readonly) NSString * baseURLString; @property(nonatomic, assign) int page; @property(nonatomic, readonly) int itemsOnPage; /*   c  URL'      */ - (id)initWithBaseURLString:(NSString *)baseURLString itemsOnPage:(int)itemsOnPage; /* ,    TTURLRequest          */ - (TTURLRequest *)requestForDataWithCachePolicy:(TTURLRequestCachePolicy)cachePolicy more:(BOOL) more; /* ,   NSData * data          */ - (NSArray *)parseDataToItems:(NSData *)data error:(NSError **)error; @end 




And, actually, we implement our model.



 @implementation ItemsListModel @synthesize items = _items; @synthesize itemsOnPage = _itemsOnPage; @synthesize baseURLString = _baseURLString; @synthesize page = _page; /*   .   .      0 */ - (id)initWithBaseURLString:(NSString *)baseURLString itemsOnPage:(int)itemsOnPage { self = [super init]; if (self) { _baseURLString = [baseURLString copy]; _itemsOnPage = itemsOnPage; _page = 0; _items = [[NSMutableArray array] retain]; _itemsOnPage = 20; } return self; } /*    */ - (void)load:(TTURLRequestCachePolicy)cachePolicy more:(BOOL)more { //  ,    ,  //     if ( ! [self isLoading]) { //    _isLoadingMore = more; //      //        if ( ! more) { [self reset]; } //        TT_RELEASE_SAFELY(_loadingRequest); _loadingRequest = [[self requestForDataWithCachePolicy:cachePolicy more:more] retain]; //    [_loadingRequest send]; } } /* ,        */ - (TTURLRequest *)requestForDataWithCachePolicy:(TTURLRequestCachePolicy)cachePolicy more:(BOOL) more { //     //      page // hhtp://baseURL/sdfsd?page=0 // //          //        NSString * fullRequestURL = self.baseURLString; fullRequestURL = [fullRequestURL stringByAddingQueryDictionary: [NSDictionary dictionaryWithObject: [NSString stringWithFormat:@"%d", _page] forKey:@"page" ] ]; //    TTURLRequest * req = [TTURLRequest requestWithURL:fullRequestURL delegate:self]; req.cachePolicy = cachePolicy; req.response = [[TTURLDataResponse new] autorelease]; return req; } /*  ,         */ - (NSArray *)parseDataToItems:(NSData *)data error:(NSError **)error { NSString * dataString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; //      SBJSON NSArray * tweets = [dataString JSONValue]; return tweets; } - (void)requestDidFinishLoad:(TTURLRequest *)request { TTURLDataResponse * response = nil; // ,        if ([[request response] isKindOfClass:[TTURLDataResponse class]]) { response = (TTURLDataResponse *)request.response; } if ( ! response) { //   -    [self request:request didFailLoadWithError:nil/*    */]; _isLoadingMore = NO; return; } NSError * error = nil; //  ,    NSArray * loadedItems = [self parseDataToItems:[response data] error:&error]; // ,       if (error) { //  ?    [self request:request didFailLoadWithError:error]; return; } //    ,  //      if ([self isLoadingMore]) { [_items addObjectsFromArray:loadedItems]; } else { TT_RELEASE_SAFELY(_items); _items = [[NSMutableArray arrayWithArray:loadedItems] retain]; } //    _page++; if ([loadedItems count] < _itemsOnPage) { /* ,          ,     */ // _hasMoreItems = NO } _isLoadingMore = NO; [super requestDidFinishLoad:request]; } /*      */ - (void)reset { _page = 0; TT_RELEASE_SAFELY(_items); [super reset]; } /*      */ - (void)dealloc { TT_RELEASE_SAFELY(_baseURLString); TT_RELEASE_SAFELY(_items); [super dealloc]; } @end 




Well, and finally, an example of using our model in real conditions

 @implementation MyModelViewController #pragma mark - View LifeCycle - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self createAndStartModelIfNeeded]; } //... #pragma mark - Model Showing - (void)showModelData:(id<TTModel>)model { //    result ItemsListModel //   NSArray * tweets = [(ItemsListModel *)model items]; //   " " NSLog(@"tweets : %{} ", tweets); //        :( //      @"tweets : %@" //    -  status Label if ([tweets count]) { NSDictionary * firstTweet = [tweets objectAtIndex:0]; NSString * tweetText = [firstTweet objectForKey:@"text"]; _statusLabel.text = [[_statusLabel text] stringByAppendingFormat:@"\n%@", tweetText]; } } #pragma mark - Model Creationg - (void)createAndStartModelIfNeeded { //   if ( ! _model) { _model = [[ItemsListModel alloc] initWithBaseURLString:@"http://api.twitter.com/1/statuses/user_timeline.json?user_id=18191307" itemsOnPage:20]; [[_model delegates] addObject:self]; } //       -    if ( ! [_model isLoaded] ) { [_model load:TTURLRequestCachePolicyNone more:NO]; } } #pragma mark - TTModel Delegate - (void)modelDidStartLoad:(id<TTModel>)model { _statusLabel.text = @""; } - (void)modelDidFinishLoad:(id<TTModel>)model { _statusLabel.text = @"!   "; [self showModelData:model]; } - (void)model:(id<TTModel>)model didFailLoadWithError:(NSError*)error { _statusLabel.text = [NSString stringWithFormat:@":(( %@", [error localizedDescription]]; } - (void)modelDidCancelLoad:(id<TTModel>)model { _statusLabel.text = @" "; } @end 




To whom it is especially interesting, source codes can be downloaded from here . I did not dare to save Habr's users from enjoying creating a project with a Three20 connection, so that only files with a model and ViewController are in the archive.



Summary





The TTModel in the Three20 library is a fairly good abstraction that can be used to organize the MVC approach when programming under iOS. Even if you are not going to use Three20, you should still look at the implementation of TTModel, and, if you wish, make your own model, based on the presented one.

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



All Articles