SweetWater Defence - еще разок о Tower Defence :) Уроки iPhone SDK: Получение доступа к адресной книге
Май 30

В предыдущей статье я рассказал о настройке модели, узнать об изменениях свойств которой можно с помощью наблюдения за ключом/значением. С предложенным методом связана одна теоретическая проблема, на которую уже указали читатели: за все изменения модели отвечает всего один метод — “observeValueForKeyPath:ofObject:change:context:“. Как следствие, наблюдая за несколькими свойствами, получаем скопление операторов “if/else” или посредством оператора выбора выясняем, какие же именно изменения только что произошли. Поразмышляв об этом, я вспомнил о предложении передавать селекторы в качестве контекста при обращении к “addObserver“. Рассмотрим, как именно это делается.

Идея заключается в том, что для каждого свойства, за которым идет наблюдение, к запросу “addObserver” добавляется селектор (в параметрах контекста). В методе “observeValueForKeyPath…” выполняем селектор, например, запускаем метод. Тем самым мы привязываем каждое свойство к определенному методу, который и будет вызываться при изменении свойства.

Принимаемся за конструирование. Первая модель относительно проста:

1
2
3
4
5
6
7
8
9
10
11
#import

@interface Model : NSObject {
BOOL propertyA;
BOOL propertyB;
}

@property BOOL propertyA;
@property BOOL propertyB;

@end

и

1
2
3
4
5
6
7
#import "Model.h"

@implementation Model
@synthesize propertyA;
@synthesize propertyB;

@end

Как видите, все ограничивается двумя булевыми свойствами — “propertyA” и “propertyB“. Пользовательский интерфейс включает две кнопки и метку.

mvc2model

Метка будет подключена к спецификатору IBOutlet с названием “label“, а кнопки — к двум функциям “IBActions” с именами “onButtonA” и “onButtonB“. Эти переменные и действия будут в контроллере представления. Вот заголовочный файл:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import
#import "Model.h"

@interface MVC2ViewController : UIViewController {
UILabel *label;
Model *model;
}

@property (nonatomic, retain) IBOutlet UILabel *label;

- (IBAction)onButtonA:(id)sender;
- (IBAction)onButtonB:(id)sender;
- (void)methodA;
- (void)methodB;

@end

Как видите, в нашем распоряжении метка, модель и два действия кнопок. Также имеется два метода — “methodA” и “methodB“, но о них чуть ниже.

Теперь реализация. Сначала код — потом объяснения.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#import "MVC2ViewController.h"

@implementation MVC2ViewController
@synthesize label;

- (void)viewDidLoad {
[super viewDidLoad];
model = [[Model alloc] init];
[model addObserver:self forKeyPath:@"propertyA" options:NSKeyValueObservingOptionNew context:@selector(methodA)];
[model addObserver:self forKeyPath:@"propertyB" options:NSKeyValueObservingOptionNew context:@selector(methodB)];
}

- (IBAction)onButtonA:(id)sender
{
model.propertyA = YES;
}

- (IBAction)onButtonB:(id)sender
{
model.propertyB = YES;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
SEL selector = (SEL)context;
[self performSelector:selector];
}

- (void)methodA
{
label.text = @"methodA was called";
}

- (void)methodB
{
label.text = @"methodB was called";
}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Освобождаем представление, если у того нет superview
// Освобождает все лишнее, включая данные в кэшэ
}

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

@end

Ну что ж. Первое задание выполнено — модель готова. Теперь нам нужно фиксировать перемены для двух значений — “propertyA” и “propertyB“. Только в этот раз в качестве контекста передадим не ноль, как раньше, а два отдельных селектора — один для “methodA“, а другой — для “methodB“:

1
2
[model addObserver:self forKeyPath:@"propertyA" options:NSKeyValueObservingOptionNew context:@selector(methodA)];
[model addObserver:self forKeyPath:@"propertyB" options:NSKeyValueObservingOptionNew context:@selector(methodB)];

Теперь при нажатии кнопок А или В, поступает запрос для “onButtonA” или “onButtonB“, который устанавливает “propertyA” или “propertyB” для модели на YES. Бессмысленный пример, но нам важен механизм происходящего, ведь, разобравшись, можно найти для него множество применений.

Теперь, когда какое-то из свойств меняется, вызывается метод “observeValueForKeyPath…“. Контекст мы указали при добавлении обозревателя — селектор для “methodA” либо для “methodB“. Для пустой операции контекст приводится к указателю, поэтому видоизмените его в селектор “SEL“, а потом передайте селектор вызванному методу “performSelector“.

1
2
3
4
5
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
SEL selector = (SEL)context;
[self performSelector:selector];
}

В результате при изменении “propertyA” будет выполняться “methodA“, при изменениях в “propertyB” — “methodB“. Никаких операторов переключения и “if/else“.

Приведенные методы просто задают для метки соответствующий текст. В реальном приложении им понадобится обновленное свойство и от самой модели. Конечно, можно получить его и непосредственно от модели или в методе “observeValueForKeyPath…” передать новое значение при выполнении селектора:

1
2
3
4
5
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
SEL selector = (SEL)context;
[self performSelector:selector withObject:change];
}

Само собой, теперь “methodA” и “methodB” нужно настроить на прием параметра. Возможно, у кого-то возникнет желание “изменить” нужный тип.

Подумайте и о других вариантах “performSelector” — в таких возможностях стоит разобраться поподробнее.

Мне кажется, самое важное при таком подходе — ОБЯЗАТЕЛЬНАЯ передача корректного селектора в контексте для КАЖДОГО добавляемого обозревателя. А если передаваемые аргументы такие, как в последнем примере, и преобразуются в определенный тип, тогда КАЖДЫЙ селектор ДОЛЖЕН быть настроен на получение одного типа аргумента. Безусловно, можно передавать их на пустую операцию и как указатели, и пусть селекторы обрабатывают их самостоятельно. Но, на мой взгляд, такой подход несколько лишен изящества. Вот почему я думаю, что лучше, чтобы метод запрашивал нужные данные непосредственно у модели, вместо того, чтобы передавать измененные значения.

Понятия не имею, принято ли то, что я предлагаю, но все представляется достаточно простым и ясным. Пока не узнаю о чем-то более удобном, буду пользоваться таким кодом. Надеюсь, поможет.

Текст оригинальной статьи на английском языке [здесь]

Уважаемые читатели, данный материал был переведен и подготовлен к публикации проектом LookApp.ru, при публикации на другом сайте ссылка на LookApp.ru обязательна.

1 звезда2 звезд3 звезд4 звезд5 звезд (2 голосов, средний: 4.50 из 5)
Загрузка ... Загрузка ...


One Response to “Уроки iPhone SDK: (Часть 2) И еще об MVC на iPhone”

  1. 1. Igor Says:

    Очень полезная статья, спасибо.
    А как насчет использования протоколов/делегирования для модели/сонтроллера? Например, в протоколе для нужных полей определить метод для делегата, а делегату реализовывать нужный. Ведь если моделей будет больше и свойств в модели пару десятков для каждого свойства нужно фактически реализовывать сеттер в контроллере (когда он и так уже есть в модели и просто нуждается в каких либо действиях контроллера). ИМХО

Оставьте комментарий