iOS. Приемы программирования Нахавандипур Вандад
• 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 является изменяемым, хотя из его названия это и не следует.
Обеспечение поддержки подписывания объектов в ваших классах
Традиционно при необходимости доступа к объектам, содержащимся в коллекциях — например, массивах и словарях, — программисту требовалось получить доступ к методу в словаре или массиве, чтобы получить или установить желаемый объект. Например, создавая изменяемый словарь, мы добавляем в него два ключа и значения, получая эти значения обратно:
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 <Foundation/Foundation.h>
/* Мы будем использовать их как ключи для наших свойств 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<NSCopying>)paramKey;
— (void) setObject:(id)paramObject forKeyedSubscript:(id<NSCopying>)paramKey;
@end
Метод objectForKeyedSubscript: будет вызываться в вашем классе всякий раз, когда программист предоставит ключ и захочет прочитать в вашем классе значение, соответствующее данному ключу. Очевидно, тот параметр, который будет вам передан, будет представлять собой ключ, по которому программист хочет считать интересующее его значение. Дополнительно к этому методу мы будем вызывать в нашем классе метод setObject: forKeyedSubscript: всякий раз, когда программист захочет задать значение для конкретного ключа. Итак, в данной реализации мы хотим проверить, ассоциированы ли заданные ключи с именами и фамилиями. Если это так, то собираемся установить/получить в нашем классе значения имени и фамилии:
#import «Person.h»
NSString *const kFirstNameKey = @"firstName";
NSString *const kLastNameKey = @"lastName";
@implementation Person
— (id) objectForKeyedSubscript:(id<NSCopying>)paramKey{
NSObject<NSCopying> *keyAsObject = (NSObject<NSCopying> *)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<NSCopying>)paramKey{
NSObject<NSCopying> *keyAsObject = (NSObject<NSCopying> *)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
Постановка задачи
Вы хотите, чтобы у ваших пользователей отобразилось сообщение, которое будет оформлено как предупреждение (Alert). Такие сообщения можно применять, чтобы попросить пользователя подтвердить выбранное действие, запросить у него имя и пароль или просто предложить ввести какой-нибудь простой текст, который вы сможете использовать в своем приложении.
Решение
Воспользуйтесь UIAlertView.
Обсуждение
Если вы сами пользуетесь 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.