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()中进行合法性检查。
相关推荐
和沐阳学逆向1 小时前
iOS逆向_古法逆向_Instagram最新版抓包
macos·ios·cocoa
自学AI的鲨鱼儿6 小时前
mac npm 安装 codex 报错 npm ENOTEMPTY
macos·npm·codex
Digitally10 小时前
如何将真我(realme)手机数据传输至 iPhone
ios·智能手机·iphone
Sephiroth.Ma14 小时前
Mac 提示“Docker 已损坏,无法打开”?我这样排查后 10 分钟修好
macos·docker·容器
量子炒饭大师14 小时前
【OpenClaw修炼宝典】—— 【macOS安装篇】想玩《爪子船长》复刻版却卡在安装?OpenClaw 从零环境搭建与编译全攻略 (小白避坑指南)
macos·openclaw·小龙虾·龙虾
JFSJHFZJ14 小时前
解密iPhone核心技术,读懂苹果的硬实力
ios·cocoa·iphone
不才小强15 小时前
macOS 屏幕录制开发完全指南:ScreenCaptureKit与音频采集实战
macos·音视频
JXSJHF16 小时前
iPhone隐藏功能大盘点,免费好用不占内存
ios·iphone
ShiLuoHeroKing1 天前
Mole:面向专业用户的Mac系统清理开源方案
macos