1. KVO 简单使用
//添加一个监听
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
//响应kvo - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
新建一个控制器 TagetViewController 控制器 和一个 Company 类
在我们的 Company 类创建一个对象 监听它的key值
Company 类
@interface Company : NSObject
@property(strong,nonatomic) NSString * companyName;
@end
#import "Company.h"
@implementation Company
@end
//在 TagetViewController 中 实例化Company并添加一个companyName属性值监听
_pany = [Company new];
[_pany addObserver:self forKeyPath:NSStringFromSelector(@selector(companyName)) options:(NSKeyValueObservingOptionNew) context:nil];
//监听回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"%@",change);
}
//简单触发监听
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
_pany.companyName = @"5555";
}
2.如何开启手动添加触发
如果我们有需求需要去手动开起KVO 我们需要在观察的对象中实现
//开启手动触发
- (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key; YES 自动 NO 手动
手动触发方法实现
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
开启手动模式
#import "Company.h"
@implementation Company
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
//return YES; // 自动
// return NO ; //手动
if ([key isEqualToString:@"companyName"]) {
return NO;
}
return YES;
}
@end
TagetViewController 控制器里面
//触发
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[_pany willChangeValueForKey:@"companyName"];
[_pany didChangeValueForKey:@"companyName"];
_pany.companyName = @"5555";
}
由此可以看出 kvo 的手动触发跟值的改变没有关系。 只是跟 willChangeValueForKey:didChangeValueForKey:的调用有关。
3.添加属性依赖
现在在我们的Company类里面新增了一个Member 类 我们的Member类中有很多属性。当每一个属性值发生改变我们都需要触发KVO 但是我们不能对每一个属性都去添加监听。所以这里我们就需要添加属性依赖。添加了依赖之后我们只需要去监听我们的me mber 对象 当他添加依赖的属性值发生改变我们就能监听。
Member类
#import <Foundation/Foundation.h>
@interface Member : NSObject
///姓名
@property (nonatomic, copy) NSString * name ;
///编号
@property (nonatomic, strong) NSString * number;
///职位
@property (nonatomic, strong) NSString * job;
@end
@implementation Member
@end
company 新增
///员工
@property (nonatomic, strong) Member * member ;
//实例化
- (instancetype)init
{
self = [super init];
if (self) {
_member = [Member new];
}
return self;
}
在 company中添加我们的依赖对象
/**添加属性依赖*/
+(NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
NSSet * keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"member"])
{
// 添加依赖对象
keyPaths = [[NSSet alloc]initWithObjects:@"_member.name",@"_member.number",@"_member.job", nil];
}
return keyPaths;
}
添加了属性依赖之后只需要监听我们的member对象 当其属性值发生改变我们都能收到监听
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
_pany = [Company new];
//只需要监听member对象
[_pany addObserver:self forKeyPath:NSStringFromSelector(@selector(member)) options:(NSKeyValueObservingOptionNew) context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"%@",[change objectForKey:@"new"]);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//_pany.member.name = @"腾讯";
// _pany.member.job = @"IOS 开发";
_pany.member.number =@"3958";
}
4.KVO底层实现(自定义KVO)
- 创建一个子类! NSKVONotyfind_XXXX
- 重写setName 方法!
- 外界改变isa指针!
新建一个nsobject 扩展类
import "NSObject+PBKVO.h"
#import <Foundation/Foundation.h>
@interface NSObject (PBKVO)
///自定义KVO
-(void)pb_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end
import "NSObject+PBKVO.m"
#import "NSObject+PBKVO.h"
#import <objc/message.h>
#import <UIKit/UIKit.h>
@implementation NSObject (PBKVO)
-(void)pb_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
{
NSString * oldName =NSStringFromClass(self.class);
NSString * newName = [@"PBKVO" stringByAppendingString:oldName];
//创建一个子类
Class myClass = objc_allocateClassPair(self.class, newName.UTF8String, 0);
objc_registerClassPair(myClass);
//重写setName方法 其实就是添加一个方法
class_addMethod(myClass, @selector(setName:), (IMP)setName, "v@:@");
//改变isa 指针
object_setClass(self, myClass);
//保存当前观察者对象 - 用弱对象
objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_ASSIGN);
}
void setName (id self,SEL _cmd, NSString * newName)
{
//调用父类的 setName方法
Class class = [self class];
//先把自己指向父类
object_setClass(self, class_getSuperclass([self class]));
objc_msgSend(self,@selector(setName:),newName);
id observer = objc_getAssociatedObject(self, @"observer");
if (observer) {
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"new":newName},nil);
}
NSLog(@"哥们 我来了");
//换回来
object_setClass(self, class);
}
@end
修改监听方法
[_company pb_addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
点击触发 也简单完成了监听回调。