概述git
KVO的方法github
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
observer
觀察者,也就是KVO的訂閱者,訂閱者必須實現協議方法(下面有).keyPath
描述將要觀察的對象的屬性,也就是被觀察者的屬性.options
KVO的屬性配置.
NSKeyValueObservingOptionNew
change字典包括改變後的值NSKeyValueObservingOptionOld
change字典包括改變前的值NSKeyValueObservingOptionInitial
註冊後馬上觸發KVO通知NSKeyValueObservingOptionPrior
值改變前是否也要通知(這個key決定了是否在改變前改變後通知兩次)context
上下文,這個會傳遞到協議方法中,用來區分消息,處理不一樣的KVO.因此應當是不一樣的.- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
刪除指定keyPath的監聽器.- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
刪除特定上下文標記的指定keyPath的監聽器.- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
keyPath
被監聽的keyPathobject
被監聽的修改後的對象,能夠獲取修改的對象的屬性change
保存信息改變的字典(可能有舊的值,新的值等context
上下文使用步驟編程
KVO使用注意事項設計模式
很是重要ide
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
回調方法.因此須要對想要的監聽對象進行區分,以便指定不一樣的邏輯._tableView
對象的contentOffset
屬性監聽.- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) { [self doSomething]; } }
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) { [self doSomethingWhenContentOffsetChanges]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } }
可是這個是要本身搞清楚,父類中到底有沒有註冊KVO.若是監聽一個對象的兩個屬性,兩個屬性的改變時分開執行的,就會觸發兩次代理方法.如圖:函數
keyPath
進行多餘一次的removeObserver
的時候會致使程序crash.這種狀況經常出如今父類有一個kvo,父類在dealloc中remove了一次,子類又remove了一次的狀況下。不要覺得這種狀況不多出現!當你封裝framework開源給別人用或者多人協做開發時是有可能出現的,並且這種crash很難發現.解決辦法就是咱們能夠分別在父類以及本類中定義各自的context字符串,這樣iOS就能知道移除的是本身的kvo,而不是父類中的kvo,避免二次remove形成crash.所有代碼atom
#import <Foundation/Foundation.h> @interface MCBuyData : NSObject @property (nonatomic, assign) NSInteger number; @property (nonatomic, assign) NSInteger money; @end
#import "MCBuyData.h" @implementation MCBuyData @end
#import <UIKit/UIKit.h> @class MCBuyData; @interface ViewController : UIViewController @property (nonatomic, strong) MCBuyData * buyData; @end
ViewController.m設計
#import "ViewController.h"
#import "Masonry.h"
#import "MCBuyData.h"
#define kNumber @"number"
#define kMoney @"money"
代理
@interface ViewController () @property (nonatomic, strong) UILabel * numberLabel; @property (nonatomic, strong) UILabel * moneyLabel; @property (nonatomic, strong) UIButton * toBuyButton; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self settingObserver]; [self initUI]; } - (void)dealloc { [self.buyData removeObserver:self forKeyPath:kNumber context:@"number"]; [self.buyData removeObserver:self forKeyPath:kMoney context:@"money"]; } #pragma mark - 點擊事件 - (void)toBuyButtonClicked { NSInteger number = [[self.buyData valueForKey:kNumber] integerValue]; number += 1; [self.buyData setValue:@(number) forKey:kNumber]; NSInteger money = [[self.buyData valueForKey:kMoney] integerValue]; money += 100; [self.buyData setValue:@(money) forKey:kMoney]; } -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { // 該change的內容記錄的是本次監聽到的屬性的改變. NSLog(@"change: %@",change); NSString * new = change[@"new"]; if (object == self.buyData && [keyPath isEqualToString:kNumber] && (context == @"number")) { self.numberLabel.text = [NSString stringWithFormat:@"次數: %@",new]; } else { // 寫了這句,若是父視圖中沒有註冊的KVO,就會崩掉. // reason: '<ViewController: 0x7fd7af406030>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. // [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } if ([keyPath isEqualToString:kMoney]) { self.moneyLabel.text = [NSString stringWithFormat:@"金額: %@",new]; } else { // [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (void)settingObserver { self.buyData = [[MCBuyData alloc] init]; [self.buyData setValue:@(0) forKey:kNumber]; [self.buyData setValue:@(0) forKey:kMoney]; [self.buyData addObserver:self forKeyPath:kNumber options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"number"]; [self.buyData addObserver:self forKeyPath:kMoney options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"money"]; } - (void)initUI { [self.view addSubview:self.numberLabel]; [self.numberLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view).with.offset(20); make.right.mas_equalTo(self.view).with.offset(-20); make.top.mas_equalTo(self.view).with.offset(100); make.height.mas_equalTo(50); }]; [self.view addSubview:self.moneyLabel]; [self.moneyLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view).with.offset(20); make.right.mas_equalTo(self.view).with.offset(-20); make.top.mas_equalTo(self.view).with.offset(250); make.height.mas_equalTo(50); }]; [self.view addSubview:self.toBuyButton]; [self.toBuyButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view).with.offset(20); make.right.mas_equalTo(self.view).with.offset(-20); make.top.mas_equalTo(self.view).with.offset(400); make.height.mas_equalTo(50); }]; } #pragma mark - setter & getter - (UILabel *)numberLabel { if (_numberLabel == nil) { self.numberLabel = [[UILabel alloc] init]; self.numberLabel.backgroundColor = [UIColor orangeColor]; self.numberLabel.font = [UIFont systemFontOfSize:15]; self.numberLabel.textColor = [UIColor whiteColor]; self.numberLabel.textAlignment = NSTextAlignmentCenter; self.numberLabel.text = @"次數: 1"; } return _numberLabel; } - (UILabel *)moneyLabel { if (_moneyLabel == nil) { self.moneyLabel = [[UILabel alloc] init]; self.moneyLabel.backgroundColor = [UIColor orangeColor]; self.moneyLabel.font = [UIFont systemFontOfSize:15]; self.moneyLabel.textColor = [UIColor whiteColor]; self.moneyLabel.textAlignment = NSTextAlignmentCenter; self.moneyLabel.text = @"金額: 1"; } return _moneyLabel; } - (UIButton *)toBuyButton { if (_toBuyButton == nil) { self.toBuyButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.toBuyButton.titleLabel.font = [UIFont systemFontOfSize:14]; self.toBuyButton.backgroundColor = [UIColor redColor]; [self.toBuyButton setTitle:@"買 買 買!!!" forState:UIControlStateNormal]; [self.toBuyButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [self.toBuyButton addTarget:self action:@selector(toBuyButtonClicked) forControlEvents:UIControlEventTouchUpInside]; } return _toBuyButton; } @end
Demo 下載地址code
https://github.com/mancongiOS/KVO.git
說明