ПРОГРАММИРОВАНИЕ ПОД IPHONE, IPAD OBJECTIVE-C часть 3

Модель данных

 

До этого примера большая часть читателей для хранения и манипулирования данными использовали словари. Давайте еще раз взглянем на пример TBXML. В это примере для хранения полученных данных с сервера мы использовали словарь (NSDictionary). То есть, заголовок новости, ссылку на нее и другие значения мы храним в одном словаре под разными ключами. С одной стороны это удобно, а с другой — накладывает определенные ограничения и трудности. В этом примере я продемонстрирую альтернативный способ хранения данных (Модель данных), чтобы вы полностью смогли оценить его превосходство над словарями.

 

Как вы поняли, задача состоит в том, чтобы заменить NSDictionary чем-то альтернативным в примере TBXML, исходный код этого примера можно скачать здесь. В первую очередь следует добавить в проект новый класс (унаследованный от NSObject). Мы это делали много раз, а если кто-то забыл — можно посмотреть здесь. Я назвал свой класс NewsItem. Теперь добавим к этому классу несколько переменных:

 

NewsItem.h

<code data-result="[object Object]">#import &lt;Foundation/Foundation.h&gt;

@interface NewsItem : NSObject {
    NSString *title;
    NSString *link;
    NSString *description;
    NSString *date;
    NSString *imageLink;
    UIImage *image;
}

@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *link;
@property (nonatomic, retain) NSString *description;
@property (nonatomic, retain) NSString *date;
@property (nonatomic, retain) NSString *imageLink;
@property (nonatomic, retain) UIImage *image;

@end</code>

 

NewsItem.m

<code data-result="[object Object]">#import "NewsItem.h"

@implementation NewsItem

@synthesize title;
@synthesize link;
@synthesize description;
@synthesize date;
@synthesize imageLink;
@synthesize image;

- (void)dealloc {
    self.title = nil;
    self.link = nil;
    self.description = nil;
    self.date = nil;
    self.imageLink = nil;
    self.image = nil;
    [super dealloc];
}

@end</code>

 

Как видите, у класса нет ни одной функции. Это связано с его назначением. Он будет хранилищем данных для новостей. То есть, вместо словаря мы будем хранить данные в переменных этого класса.

 

Теперь импортируем класс NewsItem в RootViewController (#import «NewsItem.h») и исправим ту часть года, где мы парсим xml. То есть код:

 

<code data-result="[object Object]">NSDictionary *newsItem = [NSDictionary dictionaryWithObjectsAndKeys:
                          [TBXML textForElement:title], @"title",
                          [TBXML textForElement:link], @"link",
                          [TBXML textForElement:description], @"desc",
                          [TBXML textForElement:image], @"image",
                          [TBXML textForElement:date], @"date",
                          nil];</code>

 

Заменим кодом:

 

<code data-result="[object Object]">NewsItem *newsItem = [[NewsItem new] autorelease];
newsItem.title = [TBXML textForElement:title];
newsItem.link = [TBXML textForElement:link];
newsItem.description = [TBXML textForElement:description];
newsItem.date = [TBXML textForElement:date];
newsItem.imageLink = [TBXML textForElement:image];</code>

 

Соответственно, следует исправить методы чтения данных. В методе cellForRowAtIndexPath код:

 

<code data-result="[object Object]">NSDictionary *newsItem = [rssNews objectAtIndex:indexPath.row];

cell.textLabel.text = [newsItem objectForKey:@"title"];
cell.detailTextLabel.text = [newsItem objectForKey:@"date"];</code>

 

заменим кодом:

 

<code data-result="[object Object]">NewsItem *newsItem = [rssNews objectAtIndex:indexPath.row];

cell.textLabel.text = newsItem.title;
cell.detailTextLabel.text = newsItem.date;</code>

 

А в методе didSelectRowAtIndexPath строки:

 

<code data-result="[object Object]">NSDictionary *newsItem = [rssNews objectAtIndex:indexPath.row];
detailViewController.description = [newsItem objectForKey:@"desc"];</code>

 

строками:

 

<code data-result="[object Object]">NewsItem *newsItem = [rssNews objectAtIndex:indexPath.row];
detailViewController.description = newsItem.description;</code>

 

Этих изменений достаточно для прежней функциональности приложения. Пользователь не заметит проделанной выше работы. Но если на код взлянет программист — преимущества такого кода ощутимы.

 

 

Задача Раньше Теперь
Работа с данными простых типов (NSInteger, BOOL, CGFloat) Требуется конвертировать тип в объект какого-то класса (NSString, NSNumber) Создать переменную такого типа
Чтение данных Каждый раз использовать метод objectForKey Обращаться к переменным через точку.
Узнать имена полей и тип значений, который в них хранится Следует использовать метод allKeys или выводить в консоль весь словарь. Так же весь словарь прийдется выводить в консоль чтобы посмотреть тип данных хранящихся в нем. Посмотреть в интерфейс класса, который выступает в роли хранилища данных (NewsItem.h).
На сервере изменено имя одного из ключей Имена ключей следует изменить в методе парсинга и во всех местах, где производится чтение данных под этими ключами. Изменить имена ключа в методе парсинга и рефакторингом изменить имя переменной в классе, который выступает в роли хранилища данных.

 

У класса NewsItem есть переменная, которую мы не использовали. Я предлагаю поправить эту ситуацию. В первую очередь, нам понадобиться последняя версия APIDownload. В ней я добавил несколько полезных функций, которые сейчас мы и задействуем. Первая «полезняшка» это то, что при старте скачивания мы можем указывать метод, который будет вызываться в случае удачной загрузки данных, а вторая — это то, что загрузкам можно присваивать метку (tag), по которой в дальнейшем можно ее идентифицировать.

 

Изменим метод cellForRowAtIndexPath:

 

<code data-result="[object Object]">- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle 
                                       reuseIdentifier:CellIdentifier] autorelease];
        cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
    }

    NewsItem *newsItem = [rssNews objectAtIndex:indexPath.row];

    cell.textLabel.text = newsItem.title;
    cell.detailTextLabel.text = newsItem.date;
    cell.imageView.image = newsItem.image;

    if (!cell.imageView.image) {
        APIDownload *request = [APIDownload downloadWithURL:newsItem.imageLink 
                                                   delegate:self 
                                                        sel:@selector(didDownloadImage:)];
        request.tag = indexPath.row;
    }

    return cell;
}</code>

 

В нем мы, как и прежде заполняем текстовые поля, а устанавливаем картинку, которая хранится в переменной image класса NewsItem. После чего следует проверка на валидность картинки. Если ее нет — начинаем загрузку. В метод downloadWithURL мы передаем ссылку на картинку, которую хотим скачать, указываем в качестве делегата текущий класс и передаем имя метода, который следует вызвать по окончанию загрузки. Это нужно для того, чтобы картинка не вернулась в метод, в котором мы парсим полученный xml. Чтобы «привязать» скачаную картинку к текущей ячейке — мы устанавливаем метке загрузки значение (индекс текущего элемента массива rssNews).

 

Теперь добавим сам метод didDownloadImage:

 

<code data-result="[object Object]">- (void)didDownloadImage:(APIDownload*)request {
    NewsItem *newsItem = [rssNews objectAtIndex:request.tag];
    newsItem.image = [UIImage imageWithData:request.downloadData];

    [self.tableView reloadData];
}</code>

 

В нем мы по установленой метке получаем объект класса NewsItem и устанавливаем переменной image скачаную картинку. Чтобы установленная картинка отобразилась в ячейке — перегружаем таблицу.

 

В каких случаях используется модель данных? Ее следует использовать во всех случаях работы с базами данных, при парсинге XML-файлов и в любых других случаях манимуляции с данными.

 

Исходный код этого проекта можно скачать здесь.

Comments are closed.