位置:首页 > Swift > iOS KVO隐藏用法与核心潜力解析

iOS KVO隐藏用法与核心潜力解析

时间:2026-08-21  |  作者:星际追番人  |  阅读:0

概述

在iOS开发领域,KVO(Key-Value Observing,键值观察)是一种很强大的观察者模式实现。它能够让对象监听其他对象特定属性的变化。

一旦被观察的属性发生变动,观察者就会立刻收到通知。接下来,本文将对KVO的原理、使用方式、注意事项以及优缺点展开探讨。

不为人知的iOS KVO潜力

KVO核心机制

KVO本质是通过 isa-swizzling技术实现的。具体过程可以分为以下几步。

1.动态子类创建

  • 当某个对象第一次被观察时,运行时系统会动态创建该对象类的子类,子类命名通常为 NSKVONotifying_原类名
  • 为 NSKVONotifying_原类名 重写观察属性的setter方法以及delloc、class、_isKVOA方法等

2.settter方法重写

  • 在赋值前调用 willChangeValueForKey
  • 在赋值后调用 didChangeValueForKey
// KVO 的伪代码实现
- (void)setName:(NSString *)name {
    // 1.通知属性即将变化
    [self willChangeValueForKey:@"name"];
    // 2.调用原始的 setter 方法
    _name = name;
    // 3.通知属性已经变化
    [self didChangeValueForKey:@"name"];
}
  • 子类重写class方法是为了屏蔽本身,隐藏其存在,不让开发者知道
  • 当修改实例对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数

3.修改isa指针

  • 运行时系统会将原对象的 isa 指针指向新创建的子类(NSKVONotifying_MyClass)
  • 通过修改 isa 指针,对象的实际类型变为子类,从而使得调用 setter 方法时,会优先调用子类中重写的 setter 方法

使用方式

Objective-C

下面是Objective-C中的基本使用示例。

@interface Person : NSObject
@property (nonatomic, assign) NSInteger age;
@end

@implementation Person
@end

@interface ViewController ()
@property (nonatomic, strong) Person *person;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建 Person 对象
    self.person = [[Person alloc] init];
    self.person.age = 20;
    
    // 注册 KVO
    [self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    
    // 修改 age 属性的值
    self.person.age = 30;
}

// 实现观察者的回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"age"]) {
        NSInteger oldValue = [[change objectForKey:NSKeyValueChangeOldKey] integerValue];
        NSInteger newValue = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
        NSLog(@"Person's age changed from %ld to %ld", oldValue, newValue);
    }
}

// 在不需要监听属性变化时,我们应该及时移除 KVO
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    
    // 移除 KVO
    [self.person removeObserver:self forKeyPath:@"age"];
}

@end

Swift

下面是Swift中的使用方式。

class Person: NSObject {

    @objc dynamic var age: Int
    
    init(age: Int) {
        self.age = age
    }
}

class ViewController: UIViewController {

    private var person = Person(age: 20)
    private var ageObserver: NSKeyValueObservation

    override func viewDidLoad() {
        super.viewDidLoad()
        // 注册 KVO
        ageObserver = person.observe(.age, options: [.old, .new], changeHandler: { [weak self] (person, change) in
            guard let self = self else { return }
            if let oldValue = change.oldValue, let newValue = change.newValue {
                print("Person's age changed from (oldValue) to (newValue)")
            }
        })
        
        // 修改 age 属性的值
        person.age = 30
    }
}

func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    
    ageObserver.invalidate()
    ageObserver = nil
}

注意,为了在Swift中使用KVO,我们需要在age属性前加上@objc dynamic修饰符。这样才能使age属性具有动态派发的特性,从而支持KVO

KVO “搞笑”时刻

KVO好用,但也很容易“翻车”。下面是几个常见场景。

场景一: 崩溃之王

KVO的经典翻车操作是忘记移除观察者。这样会导致观察者释放后仍收到通知,最终直接野指针崩溃。

// 手滑少写一句 removeObserver,直接喜提崩溃
[object addObserver:self forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil];
// 忘记解除,崩溃警告!
// [object removeObserver:self forKeyPath:@"value"]; 

场景二:神秘失踪的通知

如果直接修改实例变量,而不是通过setter,KVO就无法感知变化。此时它就像“什么都没看到”。

// KVO:我什么都没看到,别问我
_value = 100; 

场景三:多线程迷惑行为

KVO通知可能在任意线程出发。开发者如果忘记切回主线程更新UI,界面就可能直接卡成PPT。

优缺点

优点

KVO可以实现对象之间的解耦。一个对象可以监听另一个对象的属性改变,并及时更新自己的状态。

此外,KVO 还可以实现对象的属性验证和数据过滤等功能。

缺点

  • 需要手动移除观察者,容易导致内存泄漏
  • 回调方法比较混乱,需要手动判断该KeyPath
  • 不方便调试

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多