Урок 40 KVC - Key Value Coding. KVO - Key Value Observing.
Вводный урок перед CoreData. Программирование через ключ - значение. И наблюдатели за изменением ключа - значения.
Создаем пустой проект.
Добавляем класс Студент с именем и возрастом.
Переопределяем сеттеры и определяем метод Description:
Создаем Студената и устанавливаем свойства обычным способом.
Можно поменять свойства с помощью KVC следующим способом
Если неправильно написать ключ (несуществующий), то приложение упадет. Чтобы этого не произошло можно переопределить метод геттер valueForUndefinedKey и сеттер setValue: forUndefinedKey:
Чтобы существовала активная ссылка на нашего студенат, заведем проперти:
Определим метод который срабатывает при изменении проперти:
Создадим наблюдателя за нужным полем. Наблюдатель будет наш контроллер:
Тут же отписываемся от наблюдателя в Dealloc:
Внутри метода observeValueForKeyPath можно взять новое значение из дикшионари:
Если создать метод в студенте, который меняет проперти через iVar (_nameStudent =), то обсервер не вызовется, а если через self (self.nameStudent =), то обсервер сработает. Нужно это иметь ввиду.
Следующая часть урока.
Создаем новый класс TAGroup в котором находится массив студентов:
Создаем студентов и группу, добавляем студентов в массив группы:
С помощью KVC мы можем удалить из массива NSArray элемент, не превращая массив в NSMutableArray:
Или в одну строчку
Можно проверку делать не в общем методе (и проверять ключ), а для каждого ключа описать свой метод. Для этого убираем общий метод validateValue (иначе частный метод не сработает) и определяем частный метод для проперти .nameStudent:
Следующий момент - вывод длинны массива.
Создадим дополнительных студентов и новую группу:
Объявим новое проперти -массив групп:
Добавим в массив наши группы. Чтобы вывести длинну массива используем оператор коллекций @count:
Вводный урок перед CoreData. Программирование через ключ - значение. И наблюдатели за изменением ключа - значения.
Создаем пустой проект.
Добавляем класс Студент с именем и возрастом.
Переопределяем сеттеры и определяем метод Description:
@interface TAStudent : NSObject
@property (strong, nonatomic) NSString *nameStudent;
@property (assign, nonatomic) NSInteger ageStudent;
@end
@implementation TAStudent
- (void)setNameStudent:(NSString *)nameStudent
{
_nameStudent = nameStudent;
NSLog(@"setNameStudent: %@",nameStudent);
}
- (void)setAgeStudent:(NSInteger)ageStudent
{
_ageStudent = ageStudent;
NSLog(@"setAgeStudent: %@",@(ageStudent));
}
- (NSString *)description
{
return [NSString stringWithFormat:@"Student Name: %@, Age %@", self.nameStudent, @(self.ageStudent)];
}
Можно поменять свойства с помощью KVC следующим способом
[student setValue:@"Denis" forKey:@"nameStudent"];
В этом случае так же сработает сеттер setNameStudent.
Вот такой вывод мы получаем после запуска программы:
setNameStudent: Alex
setAgeStudent: 20
Student Name: Alex, Age 20
setNameStudent: Denis
Student Name: Denis, Age 20
Чтобы использовать KVC для примитивов нужно исползовать NSNumber, для структур (CGPoint, SGSize) - NSValue:
[student setValue:[NSNumber numberWithInteger:25] forKey:@"ageStudent"];
Можно считывать данные проперти с помощью KVC (valueForKey):
NSLog(@"name1 = %@, name2 = %@", student.nameStudent, [student valueForKey:@"nameStudent"]);
- (id)valueForUndefinedKey:(NSString *)key
{
NSLog(@"valueForUndefinedKey %@", key);
return @"Unknown";
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
NSLog(@"setValue %@ forUndefinedKey %@", value, key);
}
Переходим к KVO.
В KVO можно назначить наблюдателя на конкретное проперти любого объекта, который будет вызывать метод, когда это проперти будет меняться. Это очень удобно и интересно, т.к. можно обойтись без делегата.
@property (strong, nonatomic) TAStudent *student;
Определим метод который срабатывает при изменении проперти:
#pragma mark - Observing
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
NSLog(@"observeValueForKeyPath. keyPath: %@. object: %@, chang: %@", keyPath, object, change);
}
Создадим наблюдателя за нужным полем. Наблюдатель будет наш контроллер:
[self.student addObserver:self forKeyPath:@"nameStudent" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
Тут же отписываемся от наблюдателя в Dealloc:
- (void)dealloc
{
[self.student removeObserver:self forKeyPath:@"nameStudent"];
}
Вот результат вывода программы:
setNameStudent: Alex
observeValueForKeyPath. keyPath: nameStudent. object: Student Name: Alex, Age 0, chang: {
kind = 1;
new = Alex;
old = "<null>";
}
setAgeStudent: 20
Student Name: Alex, Age 20
Student Set value Denis for key nameStudent
setNameStudent: Denis
observeValueForKeyPath. keyPath: nameStudent. object: Student Name: Denis, Age 20, chang: {
kind = 1;
new = Denis;
old = Alex;
}
Student Set value 25 for key ageStudent
setAgeStudent: 25
name1 = Denis, name2 = Denis
Student Set value Vanya for key Key
setValue Vanya forUndefinedKey Key
Student Name: Denis, Age 25
id newValue = [change objectForKey:NSKeyValueChangeNewKey];
Если создать метод в студенте, который меняет проперти через iVar (_nameStudent =), то обсервер не вызовется, а если через self (self.nameStudent =), то обсервер сработает. Нужно это иметь ввиду.
- (void)changeName
{
NSLog(@"ChangeName to FakeName");
_nameStudent = @"FakeName";
}
Для того, чтобы обсервер увидел изменение проперти по айвар, необходимо добавить 2 метода willChangeValueForKey до изменения и didChangeValueForKey после:
- (void)changeName
{
NSLog(@"ChangeName to FakeName");
[self willChangeValueForKey:@"nameStudent"];
_nameStudent = @"FakeName";
[self didChangeValueForKey:@"nameStudent"];
}
Следующая часть урока.
Создаем новый класс TAGroup в котором находится массив студентов:
@interface TAGroup : NSObject
@property (strong, nonatomic) NSArray *arrayStudents;
@end
TAStudent *student = [[TAStudent alloc] init];
student.nameStudent = @"Alex";
student.ageStudent = 20;
TAStudent *student1 = [[TAStudent alloc] init];
student1.nameStudent = @"Stas";
student1.ageStudent = 22;
TAStudent *student2 = [[TAStudent alloc] init];
student2.nameStudent = @"Ivan";
student2.ageStudent = 21;
TAStudent *student3 = [[TAStudent alloc] init];
student3.nameStudent = @"Alexey";
student3.ageStudent = 23;
TAGroup *group1 = [[TAGroup alloc] init];
group1.arrayStudents = @[student, student1, student2, student3];
С помощью KVC мы можем удалить из массива NSArray элемент, не превращая массив в NSMutableArray:
NSMutableArray *arrayMutable = [group1 mutableArrayValueForKey:@"arrayStudents"];
[arrayMutable removeLastObject];
Или в одну строчку
[[group1 mutableArrayValueForKey:@"arrayStudents"] removeLastObject];
Так же можно использовать путь до ключа valueForKeyPath
NSLog(@"KeyPath. name = %@",[self valueForKeyPath:@"student.nameStudent"]);
Для того чтобы проверить возможность ввода определенного значения в проперти используем validateValue:
NSString *newName = @"Alex123";
NSError *error = nil;
if (![self.student validateValue:&newName forKey:@"nameStudent" error:&error]) {
NSLog(@"Validate error: %@", error);
}
В классе TAStudent переопределяем метод validateValue:
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError
{
if ([inKey isEqualToString:@"nameStudent"]) {
NSString *newName = *ioValue;
if (![newName isKindOfClass:[NSString class]]) {
*outError = [[NSError alloc] initWithDomain:@"Not Nsstring" code:123 userInfo:nil];
return NO;
}
if ([newName rangeOfString:@"1"].location != NSNotFound) {
*outError = [[NSError alloc] initWithDomain:@"Has numbers" code:1234 userInfo:nil];
return NO;
}
}
return YES;
}
При выполнение сработает проверка и выведет:
Validate error: Error Domain=Has numbers Code=1234 "(null)"
Можно проверку делать не в общем методе (и проверять ключ), а для каждого ключа описать свой метод. Для этого убираем общий метод validateValue (иначе частный метод не сработает) и определяем частный метод для проперти .nameStudent:
- (BOOL)validateNameStudent:(inout id _Nullable __autoreleasing *)ioValue error:(out NSError * _Nullable __autoreleasing *)outError
{
NSLog(@"validateNameStudent");
return YES;
}
Создадим дополнительных студентов и новую группу:
TAStudent *student5 = [[TAStudent alloc] init];
student5.nameStudent = @"Vasya";
student5.ageStudent = 21;
TAStudent *student6 = [[TAStudent alloc] init];
student6.nameStudent = @"Sergey";
student6.ageStudent = 23;
TAGroup *group2 = [[TAGroup alloc] init];
group2.arrayStudents = @[student5, student6];
Объявим новое проперти -массив групп:
@property (strong, nonatomic) NSArray *arrayGroup;
self.arrayGroup = @[group1, group2];
NSLog(@"group count %@", [self valueForKeyPath:@"arrayGroup.@count"]);
Чтобы объединить массивы студентов во всех группах и исключить одинаковые объекты с помощью одной строки кода оператор коллекций @distinctUnionOfArrays
NSArray *arrayAllStudents = [self valueForKeyPath:@"arrayGroup.@distinctUnionOfArrays.arrayStudents"];
NSLog(@"all students = %@", arrayAllStudents);
Информация по всем ключам можно посмотреть в документации Apple к KVC -> Collection Operators.
Для вывода минимального, максимального, среднего возраста студентов и суммы всех возрастов:
NSNumber *minAge = [arrayAllStudents valueForKeyPath:@"@min.ageStudent"];
NSNumber *maxAge = [arrayAllStudents valueForKeyPath:@"@max.ageStudent"];
NSNumber *sumAge = [arrayAllStudents valueForKeyPath:@"@sum.ageStudent"];
NSNumber *avgAge = [arrayAllStudents valueForKeyPath:@"@avg.ageStudent"];
NSLog(@"minAge = %@, maxAge = %@, sumAge = %@, avgAge = %@", minAge, maxAge, sumAge, avgAge);
Для объединения объектов - имен студентов в отдельный массив используем оператор коллекций @distinctUnionOfObjects:
NSArray *arrayAllNames = [arrayAllStudents valueForKeyPath:@"@distinctUnionOfObjects.nameStudent"];
NSLog(@"arrayAllNames: %@", arrayAllNames);
Исходный код к уроку 40.
Домашнее задание для урока 40 KVC, KVO.
Ученик.
1. Создайте класс студента с пропертисами firstName, lastName, dateOfBirth, gender, grade
2. Также создайте статическую таблицу куда все эти данные выводятся и где их можно менять (с текст филдами, сенгментед контролами и тд)
3. Пропертисы вашего студента меняйте тем же образом что и в предыдущих уроках (через делегаты и акшины)
Студент.
4. Повесте обсервера на все пропертисы студента и выводите в консоль каждый раз, когда проперти меняется
5. Также сделайте метод "сброс", который сбрасывает все пропертисы, а в самом методе не используйте сеттеры, сделайте все через айвары, но сделайте так, чтобы обсервер узнал когда и что меняется. (типо как в уроке)
Мастер.
забудьте про UI
6. Создайте несколько студентов и положите их в массив, но обсервер оставьте только на одном из них
7. У студентов сделайте weak проперти "friend". Сделайте цепочку из нескольких студентов, чтобы один был друг второму, второй третьему, тот четвертому, а тот первому :)
8. Используя метод setValue: forKeyPath: начните с одного студента (не того, что с обсервером) и переходите на его друга, меняя ему проперти, потом из того же студента на друга его друга и тд (то есть путь для последнего студента получится очень длинный)
9. Убедитесь что на каком-то из друзей, когда меняется какой-то проперти, срабатывает ваш обсервер
Супермен
10. Добавьте побольше студентов
11. Используя операторы KVC создайте массив имен всех студентов
12. Определите саммый поздний и саммый ранний годы рождения
13. Определите сумму всех баллов студентов и средний бал всех студентов
1. Создайте класс студента с пропертисами firstName, lastName, dateOfBirth, gender, grade
2. Также создайте статическую таблицу куда все эти данные выводятся и где их можно менять (с текст филдами, сенгментед контролами и тд)
3. Пропертисы вашего студента меняйте тем же образом что и в предыдущих уроках (через делегаты и акшины)
Студент.
4. Повесте обсервера на все пропертисы студента и выводите в консоль каждый раз, когда проперти меняется
5. Также сделайте метод "сброс", который сбрасывает все пропертисы, а в самом методе не используйте сеттеры, сделайте все через айвары, но сделайте так, чтобы обсервер узнал когда и что меняется. (типо как в уроке)
Мастер.
забудьте про UI
6. Создайте несколько студентов и положите их в массив, но обсервер оставьте только на одном из них
7. У студентов сделайте weak проперти "friend". Сделайте цепочку из нескольких студентов, чтобы один был друг второму, второй третьему, тот четвертому, а тот первому :)
8. Используя метод setValue: forKeyPath: начните с одного студента (не того, что с обсервером) и переходите на его друга, меняя ему проперти, потом из того же студента на друга его друга и тд (то есть путь для последнего студента получится очень длинный)
9. Убедитесь что на каком-то из друзей, когда меняется какой-то проперти, срабатывает ваш обсервер
Супермен
10. Добавьте побольше студентов
11. Используя операторы KVC создайте массив имен всех студентов
12. Определите саммый поздний и саммый ранний годы рождения
13. Определите сумму всех баллов студентов и средний бал всех студентов
Комментариев нет:
Отправить комментарий