面试题答案
一键面试建立属性之间的依赖关系
- 重写
+ (NSSet *)keyPathsForValuesAffectingValueForKey:
方法- 当某个属性的变化会影响到另一个属性的值时,在类中重写这个类方法。例如,如果有一个
fullName
属性依赖于firstName
和lastName
属性:
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([key isEqualToString:@"fullName"]) { NSMutableSet *affectingKeys = [NSMutableSet setWithSet:keyPaths]; [affectingKeys addObject:@"firstName"]; [affectingKeys addObject:@"lastName"]; keyPaths = [NSSet setWithSet:affectingKeys]; } return keyPaths; }
- 当某个属性的变化会影响到另一个属性的值时,在类中重写这个类方法。例如,如果有一个
- 使用
@dynamic
关键字(适用于自定义存取方法)- 如果属性使用自定义存取方法,可以在类扩展中声明属性为
@dynamic
,然后在实现存取方法时,手动调用willChangeValueForKey:
和didChangeValueForKey:
方法来通知依赖关系。例如:
@interface MyClass () @property (nonatomic, strong) NSString *firstName; @property (nonatomic, strong) NSString *lastName; @dynamic fullName; @end @implementation MyClass - (NSString *)fullName { return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName]; } - (void)setFirstName:(NSString *)firstName { [self willChangeValueForKey:@"firstName"]; _firstName = firstName; [self didChangeValueForKey:@"firstName"]; [self didChangeValueForKey:@"fullName"]; } - (void)setLastName:(NSString *)lastName { [self willChangeValueForKey:@"lastName"]; _lastName = lastName; [self didChangeValueForKey:@"lastName"]; [self didChangeValueForKey:@"fullName"]; } @end
- 如果属性使用自定义存取方法,可以在类扩展中声明属性为
手动触发KVO通知
- 调用
willChangeValueForKey:
和didChangeValueForKey:
方法- 在属性值改变之前调用
willChangeValueForKey:
,改变之后调用didChangeValueForKey:
。例如:
- (void)setSomeProperty:(id)newValue { [self willChangeValueForKey:@"someProperty"]; _someProperty = newValue; [self didChangeValueForKey:@"someProperty"]; }
- 在属性值改变之前调用
- 使用
NSKeyValueChange
枚举值(用于复杂变化)- 如果属性的变化比较复杂,如数组的插入、删除等,可以使用
NSKeyValueChange
枚举值来更精确地描述变化。例如,对于一个可变数组属性myArray
:
NSMutableArray *array = self.myArray; NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:0]; id objectToInsert = @"New Object"; [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:@"myArray"]; [array insertObject:objectToInsert atIndex:0]; [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:@"myArray"];
- 如果属性的变化比较复杂,如数组的插入、删除等,可以使用
注意问题
- 线程安全
- 在多线程环境下,KVO通知的触发需要注意线程安全。如果在不同线程中修改属性值并触发KVO,可能会导致数据竞争和未定义行为。可以使用锁(如
NSLock
、@synchronized
等)来保证在修改属性值和触发通知时的线程安全。
- 在多线程环境下,KVO通知的触发需要注意线程安全。如果在不同线程中修改属性值并触发KVO,可能会导致数据竞争和未定义行为。可以使用锁(如
- 注册和注销观察者
- 确保在合适的时机注册和注销观察者。如果没有及时注销观察者,可能会导致悬空指针引用,特别是当被观察对象被释放后,观察者仍然试图访问已释放对象的属性,从而引发程序崩溃。
- 性能影响
- 手动触发KVO通知会带来一定的性能开销,尤其是在频繁触发的情况下。尽量避免不必要的KVO通知触发,以提高程序的性能。例如,可以批量处理属性变化,然后一次性触发通知,而不是每次小变化都触发。