接上次:KVO的使用(一)
1.一个属性改变,发送多个通知
name属性改变了,主动发生通知给name和height的监听
#import "KVOBaseUsesViewController_6.h"
@interface Test_6 : NSObject
@property (nonatomic,strong)NSString *name;
@property (nonatomic,assign)double height;
@end
@implementation Test_6
/*关闭自动通知 */
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return ![key isEqualToString:@"name"];
}
- (void)setName:(NSString *)name{
[self willChangeValueForKey:@"name"];
[self willChangeValueForKey:@"height"];
_name = name;
_height = _height++;
[self didChangeValueForKey:@"name"];
[self didChangeValueForKey:@"height"];
}
@end
@interface KVOBaseUsesViewController_6 ()
@property (nonatomic,strong)Test_6 *test;
@end
@implementation KVOBaseUsesViewController_6
- (void)viewDidLoad {
[super viewDidLoad];
self.test = [[Test_6 alloc]init];
[self.test addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
[self.test addObserver:self forKeyPath:@"height" options:(NSKeyValueObservingOptionNew) context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.test.name = @"Lucky";
}
-(void)dealloc{
[self.test removeObserver:self forKeyPath:@"name"];
[self.test removeObserver:self forKeyPath:@"height"];
}
@end
2.手动执行NSKeyValueChange
#import "KVOBaseUsesViewController_7.h"
@interface Girl_7 : NSObject
@property (nonatomic,strong)NSMutableArray *clothes;
@end
@implementation Girl_7
//关闭自动通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return ![key isEqualToString:@"clothes"];
}
-(void)removeClothesAtIndexes:(NSIndexSet *)indexes{
NSLog(@"准备发送通知");
[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@"clothes"];
[_clothes removeObjectsAtIndexes:indexes];
[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@"clothes"];
NSLog(@"已经发送通知");
}
@end
@interface KVOBaseUsesViewController_7 ()
@property (nonatomic,strong)Girl_7 *girl;
@end
@implementation KVOBaseUsesViewController_7
- (void)viewDidLoad {
[super viewDidLoad];
self.girl = [[Girl_7 alloc]init];
self.girl.clothes = @[@"010101",@"000",@"111"].mutableCopy;
[self.girl addObserver:self forKeyPath:@"clothes" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self makeChange_1];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
NSArray *newArray = change[NSKeyValueChangeNewKey];
NSIndexSet *indexes = change[NSKeyValueChangeIndexesKey];
__block NSInteger i = 0;
[indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"下标 : %ld, 新值 : %@", idx, newArray[i]);
i++;
}];
}
-(void)makeChange_1{
[self.girl removeClothesAtIndexes:[NSIndexSet indexSetWithIndex:0]];
NSLog(@"结果:%@",self.girl.clothes);
}
-(void)dealloc{
[self.girl removeObserver:self forKeyPath:@"clothes"];
}
@end
3.属性依赖
监听result的值,当x或者y的值改变了就会触发result的通知
#import "KVOBaseUsesViewController_8.h"
@interface Calculator : NSObject
@property (nonatomic, assign) double x;
@property (nonatomic, assign) double y;
@property (nonatomic, readonly,assign) double result;
@end
@implementation Calculator
-(double)result{
return self.x + self.y;
}
//实现属性依赖的方法
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if ([key isEqualToString:@"result"]) {
return [NSSet setWithObjects:@"x", @"y", nil];
}else {
return [super keyPathsForValuesAffectingValueForKey:key];
}
}
@end
@interface KVOBaseUsesViewController_8 ()
@property (nonatomic,strong)Calculator *calculator;
@end
@implementation KVOBaseUsesViewController_8
- (void)viewDidLoad {
[super viewDidLoad];
self.calculator = [Calculator new];
[self.calculator addObserver:self forKeyPath:@"result" options:(NSKeyValueObservingOptionNew) context:nil];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.calculator.x = 10;
NSLog(@"改变了x,结果%f",self.calculator.result);
[NSThread sleepForTimeInterval:5];
self.calculator.y = 20;
NSLog(@"改变了y,结果%f",self.calculator.result);
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
-(void)dealloc{
[self.calculator removeObserver:self forKeyPath:@"result"];
}
@end
4.获取监听对象的内部信息
import "KVOBaseUsesViewController_9.h"
#import "Person_.h"
@interface KVOBaseUsesViewController_9 ()
@property (nonatomic,strong)Person_*p;
@end
@implementation KVOBaseUsesViewController_9
- (void)viewDidLoad {
[super viewDidLoad];
self.p = [Person_ new];
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
id info = self.p.observationInfo;
NSLog(@"%@", [info description]);
}
-(void)dealloc{
[self.p removeObserver:self forKeyPath:@"age"];
}
@end
5.重复添加监听
#import "KVOBaseUsesViewController_10.h"
#import "Person_.h"
@interface KVOBaseUsesViewController_10()
@property (nonatomic,strong)Person_*p;
@end
@implementation KVOBaseUsesViewController_10
- (void)viewDidLoad {
[super viewDidLoad];
self.p = [Person_ new];
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.p.age = 10;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
-(void)dealloc{
[self.p removeObserver:self forKeyPath:@"age"];
[self.p removeObserver:self forKeyPath:@"age"];
//添加了2次 就移除2次(多一次会炸)
//[self.p removeObserver:self forKeyPath:@"age"];
}
@end
6.防止过多移除监听对象_1
(最简单、笨的方法,通过try-catch,根本上还是未解决)
#import "KVOBaseUsesViewController_11.h"
#import "Person_.h"
@interface KVOBaseUsesViewController_11()
@property (nonatomic,strong)Person_*p;
@end
@implementation KVOBaseUsesViewController_11
- (void)viewDidLoad {
[super viewDidLoad];
self.p = [Person_ new];
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
id info = self.p.observationInfo;
NSLog(@"%@", [info description]);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.p.age = 10;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
-(void)dealloc{
[self.p removeObserver:self forKeyPath:@"age"];
[self.p removeObserver:self forKeyPath:@"age"];
//添加了2次 就移除2次(多一次会炸)
@try {
[self.p removeObserver:self forKeyPath:@"age"]; //断点依旧还是走这~~
} @catch (NSException *exception) {
NSLog(@"多删除一次");
} @finally {
}
}
@end
7.防止过多移除监听对象_2
按照6的思路,我们可以通过 runtime来拦截系统的方法,来实现try-catch
NSObject+myKVO.h
#import <Foundation/Foundation.h>
@interface NSObject (myKVO)
@end
NSObject+myKVO.m
#import "NSObject+myKVO.h"
#import <objc/runtime.h>
@implementation NSObject (myKVO)
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = [NSObject class];
//交换系统的方法
SEL originalSelector = @selector(removeObserver:forKeyPath:);
SEL swizzledSelector = @selector(myRemoveObserver:forKeyPath:);
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
BOOL didAddMethod = class_addMethod(cls,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if(didAddMethod){
class_replaceMethod(cls,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}
else{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)myRemoveObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
@try {
[self myRemoveObserver:observer forKeyPath:keyPath];
} @catch (NSException *exception) {}
}
@end
实现:
#import "KVOBaseUsesViewController_12.h"
#import "Person_.h"
#import "NSObject+myKVO.h"
@interface KVOBaseUsesViewController_12()
@property (nonatomic,strong)Person_*p;
@end
@implementation KVOBaseUsesViewController_12
- (void)viewDidLoad {
[super viewDidLoad];
self.p = [Person_ new];
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
id info = self.p.observationInfo;
NSLog(@"%@", [info description]);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.p.age = 10;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
-(void)dealloc{
[self.p removeObserver:self forKeyPath:@"age"];
[self.p removeObserver:self forKeyPath:@"age"];
[self.p removeObserver:self forKeyPath:@"age"];
}
@end
8.防止过多移除/添加监听对象_3
我们可以自己写一个来进行添加和删除的控制(替换系统的方法)
NSObject+myKVO2.h
#import <Foundation/Foundation.h>
@interface NSObject (myKVO2)
@end
NSObject+myKVO2.m
#import "NSObject+myKVO2.h"
#import <objc/runtime.h>
@implementation NSObject (myKVO2)
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = [NSObject class];
//交换系统的方法
SEL originalSelector = @selector(removeObserver:forKeyPath:context:);
SEL swizzledSelector = @selector(myRemoveObserver:forKeyPath:options:context:);
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
BOOL didAddMethod = class_addMethod(cls,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if(didAddMethod){
class_replaceMethod(cls,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}
else{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
//交换系统的方法
SEL originalSelector2 = @selector(addObserver:forKeyPath:options:context:);
SEL swizzledSelector2 = @selector(myAddObserver:forKeyPath:options:context:);
Method originalMethod2 = class_getInstanceMethod(cls, originalSelector2);
Method swizzledMethod2 = class_getInstanceMethod(cls, swizzledSelector2);
BOOL didAddMethod2 = class_addMethod(cls,
originalSelector,
method_getImplementation(swizzledMethod2),
method_getTypeEncoding(swizzledMethod2));
if(didAddMethod2){
class_replaceMethod(cls,
swizzledSelector2,
method_getImplementation(originalMethod2),
method_getTypeEncoding(originalMethod2));
}
else{
method_exchangeImplementations(originalMethod2, swizzledMethod2);
}
});
}
- (void)myRemoveObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
if ([self hasContainedObserver:observer forKeyPath:keyPath context:context]) {
[self myRemoveObserver:observer forKeyPath:keyPath options:options context:context];
}
}
-(void)myAddObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
if (![self hasContainedObserver:observer forKeyPath:keyPath context:context]) {
[self myAddObserver:observer forKeyPath:keyPath options:options context:context];
}
}
-(BOOL)hasContainedObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context{
id observationInfo = self.observationInfo;
if (observationInfo) {
NSArray *observances = [observationInfo valueForKey:@"_observances"];
for (id observance in observances) {
NSObject *_observer = [observance valueForKey:@"_observer"];
NSString *_keyPath = [[observance valueForKeyPath:@"_property"] valueForKeyPath:@"_keyPath"];
Ivar _contextIvar = class_getInstanceVariable([observance class], "_context");
void *_context = (__bridge void *)(object_getIvar(observance, _contextIvar));
if (_observer == observer && [_keyPath isEqualToString:keyPath] && _context == context) return YES;
}
}
return NO;
}
@end
实现:
#import "KVOBaseUsesViewController_13.h"
#import "Person_.h"
#import "NSObject+myKVO2.h"
@interface KVOBaseUsesViewController_13()
@property (nonatomic,strong)Person_*p;
@end
@implementation KVOBaseUsesViewController_13
- (void)viewDidLoad {
[super viewDidLoad];
self.p = [Person_ new];
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:@"1"];
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:@"2"];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.p.age = 10;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
-(void)dealloc{
[self.p removeObserver:self forKeyPath:@"age" context:@"1"];
[self.p removeObserver:self forKeyPath:@"age" context:@"2"];
[self.p removeObserver:self forKeyPath:@"age" context:@"1"];
/*这样写会炸的哟,因为只叫唤了:removeObserver:forKeyPath:context:的方法
[self.p removeObserver:self forKeyPath:@"age"];
[self.p removeObserver:self forKeyPath:@"age"];
[self.p removeObserver:self forKeyPath:@"age"];
*/
}
@end
友情链接:
网友评论