MST
星途 面试题库

面试题:Objective-C中KVO的依赖关系及手动触发机制

在Objective-C的KVO(Key - Value Observing)机制中,如何建立属性之间的依赖关系?如果要手动触发KVO通知,应该怎么做,需要注意哪些问题?
28.8万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

建立属性之间的依赖关系

  1. 重写+ (NSSet *)keyPathsForValuesAffectingValueForKey:方法
    • 当某个属性的变化会影响到另一个属性的值时,在类中重写这个类方法。例如,如果有一个fullName属性依赖于firstNamelastName属性:
    + (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;
    }
    
  2. 使用@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通知

  1. 调用willChangeValueForKey:didChangeValueForKey:方法
    • 在属性值改变之前调用willChangeValueForKey:,改变之后调用didChangeValueForKey:。例如:
    - (void)setSomeProperty:(id)newValue {
        [self willChangeValueForKey:@"someProperty"];
        _someProperty = newValue;
        [self didChangeValueForKey:@"someProperty"];
    }
    
  2. 使用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"];
    

注意问题

  1. 线程安全
    • 在多线程环境下,KVO通知的触发需要注意线程安全。如果在不同线程中修改属性值并触发KVO,可能会导致数据竞争和未定义行为。可以使用锁(如NSLock@synchronized等)来保证在修改属性值和触发通知时的线程安全。
  2. 注册和注销观察者
    • 确保在合适的时机注册和注销观察者。如果没有及时注销观察者,可能会导致悬空指针引用,特别是当被观察对象被释放后,观察者仍然试图访问已释放对象的属性,从而引发程序崩溃。
  3. 性能影响
    • 手动触发KVO通知会带来一定的性能开销,尤其是在频繁触发的情况下。尽量避免不必要的KVO通知触发,以提高程序的性能。例如,可以批量处理属性变化,然后一次性触发通知,而不是每次小变化都触发。