iOS —— 初识KVO

iOS ------ 初始KVO

KVO的基础

1. KVO概念

KVO是一种开发模式,它的全称是Key-Value Observing (观察者模式) 是苹果Fundation框架下提供的一种开发机制,使用KVO,可以方便地对指定对象的某个属性进行观察,当属性发生变化时,进行通知,告诉开发者属性旧值和新值对应的内容。

2. KVO使用步骤

注册KVO监听

通过addObserver:forKeyPath:options:context:方法注册KVO,这样可以接收到keyPath属性的变化事件;

  • observer:观察者,监听属性变化的对象。该对象必须实现observeValueForKeyPath:ofObject:change:context: 方法。
  • keyPath:要观察的属性名称。要和属性声明的名称一致。
  • options:回调方法中收到被观察者的属性的旧值或新值等,对KVO机制进行配置,修改KVO通知的时机以及通知的内容。
  • context:传入任意类型的对象,在"接收消息回调"的代码中可以接收到这个对象,是KVO中的一种传值方式。

实现KVO监听

通过方法observeValueForKeyPath:ofObject:change:context:实现KVO的监听;

  • keyPath:被观察对象的属性。
  • object:被观察的对象。
  • change:字典,存放相关的值,根据options传入的枚举来返回新值旧值。
  • context:注册观察者的时候,context传递过来的值。

销毁KVO监听

在不需要监听的时候,通过方法**removeObserver:forKeyPath:,**移除监听。

3. KVO基本用法

我们可以通过对button的背景颜色进行监听,当背景颜色改变的时候,分别打印出背景颜色的前后变化的数值。

objectivec 复制代码
self.button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.button.frame = CGRectMake(150, 150, 100, 100);
    self.button.backgroundColor = UIColor.yellowColor;
    [self.view addSubview:self.button];
    
    [self.button addTarget:self action:@selector(press:) forControlEvents:UIControlEventTouchUpInside];
    
    [self.button addObserver:self forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
objectivec 复制代码
- (void)press {
    //改变被监听对象的值
    [self.kvoButton setValue:[UIColor colorWithRed:arc4random() % 255 / 255.0 green:arc4random() % 255 / 255.0 blue:arc4random() % 250 / 250.0 alpha:1] forKey:@"backgroundColor"];
}
objectivec 复制代码
//当属性变化时会激发该监听方法
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    //打印监听结果
    if ([keyPath isEqual:@"backgroundColor"]) {
        NSLog(@"old value is: %@", [change objectForKey:@"old"]);
        NSLog(@"new value is: %@", [change objectForKey:@"new"]);
    }
}

我们点击一次button:

4. KVO传值

KVO传值也很简单,可以理解为我们对第二个viewController的某一个属性做一个监听,当我们跳转到第一个viewController的时候就可以监听到值的改变。KVO传值也很简单,可以理解为我们对第二个viewController的某一个属性做一个监听,当我们跳转到第一个viewController的时候就可以监听到值的改变。

objectivec 复制代码
//第一个视图部分
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    
    self.label = [[UILabel alloc] init];
    self.label.text = @"还没传值";
    self.label.frame = CGRectMake(150, 300, 100, 25);
    
    self.button = [UIButton buttonWithType:UIButtonTypeCustom];
    self.button.frame = CGRectMake(150, 150, 100, 100);
    self.button.backgroundColor = UIColor.blueColor;
    [self.button addTarget:self action:@selector(press) forControlEvents:UIControlEventTouchUpInside];
    
    [self.view addSubview:self.button];
    [self.view addSubview:self.label];
    
}

- (void) press {
    secondViewController* second = [[secondViewController alloc] init];
    second.modalPresentationStyle = UIModalPresentationFullScreen;
    [second addObserver:self forKeyPath:@"context" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    
    [self presentViewController:second animated:YES completion:nil];
}

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqual:@"context"]) {
        id value = [change objectForKey:@"new"];
        self.label.text = value;
        
    }
}
objectivec 复制代码
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.view.backgroundColor = [UIColor orangeColor];
    self.backButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.backButton.frame = CGRectMake(100, 100, 100, 100);
    self.backButton.backgroundColor = [UIColor blueColor];
    [self.backButton addTarget:self action:@selector(pressBack) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.backButton];
    
    self.textField = [[UITextField alloc] initWithFrame:CGRectMake(100, 250, 200, 50)];
    self.textField.keyboardType = UIKeyboardTypeDefault;
    self.textField.borderStyle = UITextBorderStyleRoundedRect;
    [self.view addSubview:self.textField];
    
}

- (void) pressBack {
    self.context = self.textField.text;
    [self dismissViewControllerAnimated:YES completion:nil];
}

运行的结果如下:

刚进入的页面:

然后通过按钮进入到下一个界面。

在textFiled中写入文本,

然后返回到前一个界面,之后会发现上一个界面中的值传到了这个界面中,如下图:

禁止KVO的方法

objectivec 复制代码
//返回NO禁止KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"content"]) {
        return NO;
    } else {
        return [super automaticallyNotifiesObserversForKey:key];
    }
}

注意事项:

  • 调用removeObserver:forKeyPath:需要在观察者消失之前,否则会导致Crash。
  • 在调用addObserver方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash。
  • 观察者需要实现observeValueForKeyPath:ofObject:change:context:方法,当KVO事件到来时会调用这个方法,如果没有实现会导致Crash。
  • KVO的addObserver和removeObserver需要是成对的,如果重复remove则会导致NSRangeException类型的Crash,如果忘记remove则会在观察者释放后再次接收到KVO回调时Crash。
  • 在调用KVO时需要传入一个keyPath,由于keyPath是字符串的形式,所以其对应的属性发生改变后,字符串没有改变容易导致Crash。我们可以利用系统的反射机制将keyPath反射出来,这样编译器可以在@selector()中进行合法性检查。
相关推荐
元媛媛几秒前
如何安装Claude Code|VS Code Mac版
macos
Layer21 分钟前
从 WWDC 26 空间重构(Spatial Reframing)再看端侧 2D 转 3D 的技术演进
ios·aigc
Cutecat_10 小时前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
2601_9618454214 小时前
法考真题及答案解析|历年真题|资料已整理
linux·windows·ubuntu·macos·centos·gnu
大熊猫侯佩14 小时前
WWDC26 SwiftUI 进化之路:砸碎黑盒,彻底迎来开发自由!
ios·swiftui·swift
游戏开发爱好者816 小时前
iPhone真机调试有哪些方法?一次定位推送权限问题时整理出来的几种方案
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
Allen Su16 小时前
【Mac 教程系列第 20 篇】macOS 鼠须管(Squirrel)皮肤大全(持续更新)
macos·rime·squirrel·rime 输入法皮肤大全
LinMin_Rik19 小时前
Mac上获取私钥证书P12文件(也可以给win11的HbuilderX使用)
macos
音视频牛哥21 小时前
macOS如何实现RTSP/RTMP低延迟播放? SmartMacPlayer技术实战探究
macos·大牛直播sdk·mac rtsp播放器·mac rtmp·mac rtmp播放器·mac平台播放rtsp·mac平台播放rtmp
大熊猫侯佩1 天前
WWDC26 最被忽视的王炸:告别“伪并发”陷阱,Swift 6.4 的 async defer
ios·swift·编程语言