Современная электронная библиотека ModernLib.Net

Бестселлеры O`Reilly - iOS. Приемы программирования

ModernLib.Net / Программирование / Вандад Нахавандипур / iOS. Приемы программирования - Чтение (Ознакомительный отрывок) (стр. 3)
Автор: Вандад Нахавандипур
Жанр: Программирование
Серия: Бестселлеры O`Reilly

 

 


Макрос __unused приказывает компилятору «не жаловаться», когда переменная – в нашем случае переменная firstString – объявлена, но ни разу не использовалась. По умолчанию в такой ситуации компилятор выдает в консоль предупреждение, сообщающее, что переменная не используется. В нашем кратком примере мы объявили переменные, но не задействовали их. Поэтому, если добавить вышеупомянутый макрос в начале объявления переменной, это вполне устроит и нас, и компилятор.

Изменяемый массив – это такой массив, в который можно вносить изменения уже после того, как он был создан. Как мы видели ранее, неизменяемый массив не может быть дополнен новой информацией уже после создания. Вот пример неизменяемого массива:


NSString *string1 = @"String 1";

NSString *string2 = @"String 2";

NSString *string3 = @"String 3";


NSArray *immutableArray = @[string1, string2, string3];


NSMutableArray *mutableArray = [[NSMutableArray alloc]

initWithArray: immutableArray];


[mutableArray exchangeObjectAtIndex:0 withObjectAtIndex:1];

[mutableArray removeObjectAtIndex:1];

[mutableArray setObject: string1 atIndexedSubscript:0];

NSLog(@"Immutable array = %@", immutableArray);

NSLog(@"Mutable Array = %@", mutableArray);

Вывод этой программы таков:

Immutable array = (

"String 1",

"String 2",

"String 3"

)

Mutable Array = (

"String 1",

"String 3"

)


Еще одна распространенная коллекция, которая часто встречается в программах для iOS, – это словарь. Словари похожи на массивы, но каждому объекту в словаре присваивается ключ, и по этому ключу вы можете позже получить интересующий вас объект. Рассмотрим пример:


NSDictionary *personInformation =

@{

@"firstName": @"Mark",

@"lastName": @"Tremonti",

@"age": @30,

@"sex": @"Male"

};


NSString *firstName = personInformation[@"firstName"];

NSString *lastName = personInformation[@"lastName"];

NSNumber *age = personInformation[@"age"];

NSString *sex = personInformation[@"sex"];


NSLog(@"Full name = %@ %@", firstName, lastName);

NSLog(@"Age = %@, Sex = %@", age, sex);

А вот и вывод этой программы:

Full name = Mark Tremonti

Age = 30, Sex = Male


Можно также использовать изменяемые словари, которые довольно сильно похожи на изменяемые массивы. Содержимое изменяемого словаря можно изменить после того, как словарь инстанцирован. Пример:


NSDictionary *personInformation =

@{

@"firstName": @"Mark",

@"lastName": @"Tremonti",

@"age": @30,

@"sex": @"Male"

};


NSMutableDictionary *mutablePersonInformation =

[[NSMutableDictionary alloc] initWithDictionary: personInformation];

mutablePersonInformation[@"age"] = @32;


NSLog(@"Information = %@", mutablePersonInformation);

Вывод этой программы таков:

Information = {

age = 32;

firstName = Mark;

lastName = Tremonti;

sex = Male;

}


Еще можно работать с множествами. Множества похожи на массивы, но любой объект, входящий в состав множества, должен встречаться в нем только один раз. Иными словами, в одном множестве не может быть двух экземпляров одного и того же объекта. Пример множества:


NSSet *shoppingList = [[NSSet alloc] initWithObjects:

@"Milk",

@"Bananas",

@"Bread",

@"Milk", nil];


NSLog(@"Shopping list = %@", shoppingList);

Запустив эту программу, вы получите следующий вывод:

Shopping list = {(

Milk,

Bananas,

Bread

)}


Обратите внимание: элемент Milk упомянут в программе дважды, а в множество добавлен всего один раз. Эта черта множеств – настоящее волшебство. Изменяемые множества можно использовать и вот так:


NSSet *shoppingList = [[NSSet alloc] initWithObjects:

@"Milk",

@"Bananas",

@"Bread",

@"Milk", nil];


NSMutableSet *mutableList = [NSMutableSet setWithSet: shoppingList];


[mutableList addObject:@"Yogurt"];

[mutableList removeObject:@"Bread"];

NSLog(@"Original list = %@", shoppingList);

NSLog(@"Mutable list = %@", mutableList);

А вывод будет таким:

Original list = {(

Milk,

Bananas,

Bread

)}

Mutable list = {(

Milk,

Bananas,

Yogurt

)}


Обсуждая множества и коллекции, следует упомянуть еще два важных класса, о которых вам необходимо знать:

NSOrderedSet – неизменяемое множество, учитывающее, в каком порядке в него добавлялись объекты;

• NSMutableOrderedSet – изменяемый вариант вышеупомянутого изменяемого множества.

По умолчанию множества не учитывают, в каком порядке объекты в них добавлялись. Рассмотрим пример:


NSSet *setOfNumbers = [NSSet setWithArray:@[@3, @4, @1, @5, @10]];

NSLog(@"Set of numbers = %@", setOfNumbers);

Запустив эту программу, получим на экране следующий вывод:

Set of numbers = {(

5,

10,

3,

4,

1

)}


Но на самом деле мы наполняли множество элементами в другом порядке. Если вы хотите сохранить правильный порядок, просто воспользуйтесь классом NSOrderedSet:


NSOrderedSet *setOfNumbers = [NSOrderedSet orderedSetWithArray

:@[@3, @4, @1, @5, @10]];


NSLog(@"Ordered set of numbers = %@", setOfNumbers);

Разумеется, вы можете воспользоваться и изменяемой версией упорядоченного множества:

NSMutableOrderedSet *setOfNumbers =

[NSMutableOrderedSet orderedSetWithArray:@[@3, @4, @1, @5, @10]];


[setOfNumbers removeObject:@5];

[setOfNumbers addObject:@0];

[setOfNumbers exchangeObjectAtIndex:1 withObjectAtIndex:2];


NSLog(@"Set of numbers = %@", setOfNumbers);

А вот и результаты:

Set of numbers = {(

3,

1,

4,

10,

0

)}


Прежде чем завершить разговор о множествах, упомяну еще об одном удобном классе, который может вам пригодиться. Класс NSCountedSet может несколько раз содержать уникальный экземпляр объекта. Правда, в нем эта задача решается иначе, нежели в массивах. В массиве может несколько раз присутствовать один и тот же объект. А в рассматриваемом здесь «подсчитываемом множестве» каждый объект появляется в множестве как будто заново, но множество ведет подсчет того, сколько раз объект был добавлен в множество, и снижает значение этого счетчика на единицу, как только вы удалите из этого множества экземпляр данного объекта. Вот пример:


NSCountedSet *setOfNumbers = [NSCountedSet setWithObjects:

@10, @20, @10, @10, @30, nil];


[setOfNumbers addObject:@20];

[setOfNumbers removeObject:@10];


NSLog(@"Count for object @10 = %lu",

(unsigned long)[setOfNumbers countForObject:@10]);


NSLog(@"Count for object @20 = %lu",

(unsigned long)[setOfNumbers countForObject:@20]);

Вывод программы:

Count for object @10 = 2

Count for object @20 = 2

Класс NSCountedSet является изменяемым, хотя из его названия это и не следует.

<p>Обеспечение поддержки подписывания объектов в ваших классах</p>

Традиционно при необходимости доступа к объектам, содержащимся в коллекциях – например, массивах и словарях, – программисту требовалось получить доступ к методу в словаре или массиве, чтобы получить или установить желаемый объект. Например, создавая изменяемый словарь, мы добавляем в него два ключа и значения, получая эти значения обратно:


NSString *const kFirstNameKey = @"firstName";

NSString *const kLastNameKey = @"lastName";


NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];

[dictionary setValue:@"Tim" forKey: kFirstNameKey];

[dictionary setValue:@"Cook" forKey: kLastNameKey];


__unused NSString *firstName = [dictionary valueForKey: kFirstNameKey];

__unused NSString *lastName = [dictionary valueForKey: kLastNameKey];

Но с развитием компилятора LLVM этот код можно сократить, придав ему следующий вид:

NSString *const kFirstNameKey = @"firstName";

NSString *const kLastNameKey = @"lastName";


NSDictionary *dictionary = @{

kFirstNameKey: @"Tim",

kLastNameKey: @"Cook",

};


__unused NSString *firstName = dictionary[kFirstNameKey];

__unused NSString *lastName = dictionary[kLastNameKey];

Как видите, мы инициализируем словарь, давая ключи в фигурных скобках. Точно так же можно поступать и с массивами. Вот как мы обычно создаем и используем массивы:

NSArray *array = [[NSArray alloc] initWithObjects:@"Tim", @"Cook", nil];

__unused NSString *firstItem = [array objectAtIndex:0];

__unused NSString *secondObject = [array objectAtIndex:1];

А теперь, имея возможность подписывать объекты, мы можем сократить этот код следующим образом:

NSArray *array = @[@"Tim", @"Cook"];

__unused NSString *firstItem = array[0];

__unused NSString *secondObject = array[0];


Компилятор LLVM не останавливается и на этом. Вы можете также добавлять подписывание и к собственным классам. Существует два типа подписывания:

подписывание по ключу – действуя таким образом, вы можете задавать внутри объекта значение для того или иного ключа точно так же, как вы делали бы это в словаре. Указывая ключ, вы также можете получать доступ к значениям внутри объекта и считывать их;

• подписывание по индексу – как и при работе с массивами, вы можете устанавливать/получать значения внутри объекта, предоставив для этого объекта индекс. Это целесообразно делать в массивоподобных классах, где элементы естественным образом располагаются в порядке, удобном для индексирования.

Сначала рассмотрим пример подписывания по ключу. Для этого создадим класс под названием Person, имеющий свойства firstName и lastName. Далее мы позволим программисту менять значения этих свойств (имя и фамилию), просто предоставив ключи для этих свойств.

Вам может понадобиться добавить к классу подобный механизм подписывания по ключу, например, по такой причине: имена ваших свойств могут изменяться и вы хотите предоставить программисту возможность устанавливать значения таких свойств, не учитывая, будут ли имена этих свойств впоследствии изменяться. В противном случае программисту лучше будет использовать свойства напрямую. Другая причина реализации подписывания по ключу – стремление скрыть точную реализацию/объявление ваших свойств от программиста и закрыть программисту прямой доступ к этим свойствам.

Чтобы обеспечить поддержку подписывания по ключу в ваших собственных классах, вы должны реализовать в вашем классе два следующих метода и записать сигнатуры методов в файле заголовков этого класса. В противном случае компилятор не узнает, что в вашем классе поддерживается подписывание по ключу.


#import 


/* Мы будем использовать их как ключи для наших свойств firstName

и lastName, так что если имена наших свойств firstName и lastName

в будущем изменятся в реализации, нам не придется ничего переделывать

и наш класс останется работоспособным, поскольку мы сможем просто

изменить значения этих констант в нашем файле реализации */

extern NSString *const kFirstNameKey;

extern NSString *const kLastNameKey;


@interface Person: NSObject


@property (nonatomic, copy) NSString *firstName;

@property (nonatomic, copy) NSString *lastName;


– (id) objectForKeyedSubscript:(id)paramKey;

– (void) setObject:(id)paramObject forKeyedSubscript:(id)paramKey;


@end


Метод objectForKeyedSubscript: будет вызываться в вашем классе всякий раз, когда программист предоставит ключ и захочет прочитать в вашем классе значение, соответствующее данному ключу. Очевидно, тот параметр, который будет вам передан, будет представлять собой ключ, по которому программист хочет считать интересующее его значение. Дополнительно к этому методу мы будем вызывать в нашем классе метод setObject: forKeyedSubscript: всякий раз, когда программист захочет задать значение для конкретного ключа. Итак, в данной реализации мы хотим проверить, ассоциированы ли заданные ключи с именами и фамилиями. Если это так, то собираемся установить/получить в нашем классе значения имени и фамилии:


#import "Person.h"


NSString *const kFirstNameKey = @"firstName";

NSString *const kLastNameKey = @"lastName";


@implementation Person


– (id) objectForKeyedSubscript:(id)paramKey{


NSObject *keyAsObject = (NSObject *)paramKey;

if ([keyAsObject isKindOfClass: [NSString class]]){

NSString *keyAsString = (NSString *)keyAsObject;

if ([keyAsString isEqualToString: kFirstNameKey] ||

[keyAsString isEqualToString: kLastNameKey]){

return [self valueForKey: keyAsString];

}

}


return nil;

}


– (void) setObject:(id)paramObject forKeyedSubscript:(id)paramKey{

NSObject *keyAsObject = (NSObject *)paramKey;

if ([keyAsObject isKindOfClass: [NSString class]]){

NSString *keyAsString = (NSString *)keyAsObject;

if ([keyAsString isEqualToString: kFirstNameKey] ||

[keyAsString isEqualToString: kLastNameKey]){

[self setValue: paramObject forKey: keyAsString];

}

}

}


@end


Итак, в этом коде мы получаем ключ в методе objectForKeyedSubscript:, а в ответ должны вернуть объект, который ассоциирован в нашем экземпляре с этим ключом. Ключ, который получаем, – это объект, соответствующий протоколу NSCopying. Это означает, что при желании мы можем сделать копию такого объекта. Рассчитываем на то, что ключ будет представлять собой строку, чтобы мы могли сравнить его с готовыми ключами, которые были заранее объявлены в начале класса. В случае совпадения зададим значение данного свойства в этом классе. После этого воспользуемся методом valueForKey:, относящимся к объекту NSObject, чтобы вернуть значение, ассоциированное с заданным ключом. Но, разумеется, прежде, чем так поступить, мы должны гарантировать, что данный ключ – один из тех, которые мы ожидаем. В методе setObject: forKeyedSubscript: мы делаем совершенно противоположное – устанавливаем значения для заданного ключа, а не возвращаем их.

Теперь в любой части вашего приложения вы можете инстанцировать объект типа Person и использовать заранее определенные ключи kFirstNameKey и kLastNameKey, чтобы изменить значения свойств firstName и lastName, вот так:


Person *person = [Person new];

person[kFirstNameKey] = @"Tim";

person[kLastNameKey] = @"Cook";

__unused NSString *firstName = person[kFirstNameKey];

__unused NSString *lastName = person[kLastNameKey];

Этот код позволяет достичь точно того же результата, что и при более лобовом подходе, когда мы устанавливаем свойства класса:

Person *person = [Person new];

person.firstName = @"Tim";

person.lastName = @"Cook";

__unused NSString *firstName = person.firstName;

__unused NSString *lastName = person.lastName;


Вы также можете поддерживать и подписывание по индексу – точно как при работе с массивами. Как было указано ранее, это полезно делать, чтобы обеспечивать программисту доступ к объектам, выстраиваемым в классе в некоем естественном порядке. Но, кроме массивов, существует не так уж много структур данных, где целесообразно упорядочивать и нумеровать элементы, чего не скажешь о подписывании по ключу, которое применяется в самых разных структурах данных. Поэтому пример, которым иллюстрируется подписывание по индексу, немного надуман. В предыдущем примере у нас существовал класс Person с именем и фамилией. Теперь мы хотим предоставить программистам возможность считывать имя, указывая индекс 0, а фамилию – указывая индекс 1. Все, что требуется сделать для этого, – объявить методы objectAtIndexedSubscript: и setObject: atIndexedSubscript: в заголовочном файле класса, а затем написать реализацию. Вот как мы объявляем два этих метода в заголовочном файле класса Person:

– (id) objectAtIndexedSubscript:(NSUInteger)paramIndex;

– (void) setObject:(id)paramObject atIndexedSubscript:(NSUInteger)paramIndex;

Реализация также довольно проста. Мы берем индекс и оперируем им так, как это требуется в нашем классе. Ранее мы решили, что у имени должен быть индекс 0, а у фамилии – индекс 1. Итак, получаем индекс 0 для задания значения, присваиваем значение имени первому входящему объекту и т. д.:


– (id) objectAtIndexedSubscript:(NSUInteger)paramIndex{


switch (paramIndex){

case 0:{

return self.firstName;

break;

}

case 1:{

return self.lastName;

break;

}

default:{

[NSException raise:@"Invalid index" format: nil];

}

}


return nil;

}


– (void) setObject:(id)paramObject atIndexedSubscript:(NSUInteger)paramIndex{

switch (paramIndex){

case 0:{

self.firstName = paramObject;

break;

}

case 1:{

self.lastName = paramObject;

break;

}

default:{

[NSException raise:@"Invalid index" format: nil];

}

}

}

Теперь можно протестировать весь написанный ранее код вот так:

Person *person = [Person new];

person[kFirstNameKey] = @"Tim";

person[kLastNameKey] = @"Cook";

NSString *firstNameByKey = person[kFirstNameKey];

NSString *lastNameByKey = person[kLastNameKey];


NSString *firstNameByIndex = person[0];

NSString *lastNameByIndex = person[1];


if ([firstNameByKey isEqualToString: firstNameByIndex] &&

[lastNameByKey isEqualToString: lastNameByIndex]){

NSLog(@"Success");

} else {

NSLog(@"Something is not right");

}


Если вы правильно выполнили все шаги, описанные в этом разделе, то на консоли должно появиться значение Success.

1.1. Отображение предупреждений с помощью UIAlertView

<p>Постановка задачи</p>

Вы хотите, чтобы у ваших пользователей отобразилось сообщение, которое будет оформлено как предупреждение (Alert). Такие сообщения можно применять, чтобы попросить пользователя подтвердить выбранное действие, запросить у него имя и пароль или просто предложить ввести какой-нибудь простой текст, который вы сможете использовать в своем приложении.

<p>Решение</p>

Воспользуйтесь UIAlertView.

<p>Обсуждение</p>

Если вы сами пользуетесь iOS, то вам определенно попадались виды-предупреждения. Пример такого вида показан на рис. 1.1.


Рис. 1.1. Вид-предупреждение, сообщающий пользователю, что для работы требуется активное соединение с Интернетом


Наилучший способ инициализации вида-предупреждения заключается, разумеется, в использовании его базового конструктора-инициализатора:


– (void) viewDidAppear:(BOOL)paramAnimated{


[super viewDidAppear: paramAnimated];


UIAlertView *alertView = [[UIAlertView alloc]

initWithTitle:@"Alert"

message:@"You've been delivered an alert"

delegate: nil

cancelButtonTitle:@"Cancel"

otherButtonTitles:@"OK", nil];

[alertView show];

}


Когда этот вид-предупреждение отобразится у пользователя, он увидит экран, подобный показанному на рис. 1.2.


Рис. 1.2. Простой вид-предупреждение, отображаемый у пользователя


Чтобы показать пользователю вид-предупреждение, мы используем метод предупреждения show. Рассмотрим описания всех параметров, которые могут быть переданы базовому конструктору-инициализатору вида-предупреждения:

• title – строка, которую пользователь увидит в верхней части вида-предупрежения. На рис. 1.2 эта строка – Title;

• message – сообщение, которое отображается у пользователя. На рис. 1.2 для этого сообщения задано значение Message;

• delegate – опциональный объект-делегат, который мы передаем виду-предупреждению. Затем этот объект будет получать уведомление при каждом изменении состояния предупреждения, например, когда пользователь нажмет на экранную кнопку, изображенную в этом виде. Объект, передаваемый данному параметру, должен соответствовать протоколу UIAlertViewDelegate;

• cancelButtonTitle – строка, которая будет присваиваться кнопке отмены (Cancel Button) в виде-предупреждении. Если в виде-предупреждении есть кнопка отмены, то такой вид обычно побуждает пользователя к действию. Если пользователь не хочет совершать предложенное действие, то он нажимает кнопку отмены. Причем на этой кнопке не обязательно должна быть строка-надпись Cancel (Отменить). Надпись для этой кнопки определяете вы сами, и этот параметр опциональный – можно сделать диалоговое окно и без кнопки Отмена;

• otherButtonTitles – надписи на других кнопках, тех, которые вы хотите отобразить в виде-предупреждении. Разделяйте такие надписи запятыми. Нужно убедиться, что в конце списка названий стоит значение nil, называемое сигнальной меткой. Этот параметр не является обязательным.

Можно создать предупреждение вообще без кнопок. Но такое окно пользователь никак не сможет убрать с экрана. Создавая такой вид, вы как программист должны позаботиться о том, чтобы он убирался автоматически, например, через 3 секунды после того, как появится. Вид-предупреждение без кнопок, который не убирается автоматически, – это настоящее бедствие, с точки зрения пользователя. Ваше приложение не только получит низкие оценки на App Store за то, что вид-предупреждение блокирует пользовательский интерфейс. Велика вероятность, что вашу программу вообще удалят с рынка.

Виды-предупреждения можно оформлять с применением различных стилей. В классе UIAlertView есть свойство alertViewStyle типа UIAlertViewStyle:


typedef NS_ENUM(NSInteger, UIAlertViewStyle) {

UIAlertViewStyleDefault = 0,

UIAlertViewStyleSecureTextInput,

UIAlertViewStylePlainTextInput,

UIAlertViewStyleLoginAndPasswordInput

};


Вот что делает каждый из этих стилей:

• UIAlertViewStyleDefault – стандартный стиль вида-предупреждения, подобное оформление мы видели на рис. 1.2;

• UIAlertViewStyleSecureTextInput – при таком стиле в виде-предупреждении будет содержаться защищенное текстовое поле, которое станет скрывать от зрителя символы, вводимые пользователем. Такой вариант предупреждения вам подойдет, например, если вы запрашиваете у пользователя его учетные данные для дистанционного банковского обслуживания;


  • Страницы:
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15