使用KVO遇到的痛点
作为一个iOS开发老司机,我在日常使用KVO过程中发现了KVO的一些不足:
1、调用复杂,算上调用者,总共涉及到5个参数
2、响应函数与调用地方分隔在不同的地方,阅读代码需要跳转
3、一个观察者观察了多个对象后,回调里不好区分究竟是哪个变量发生了回调
4、需要手动释放KVO,过多的调用removeObserver会导致闪退
5、对于极其频繁的变量变动,我们无法屏蔽一些过于密集的KVO调用
解决思路
鉴于KVO以上的种种缺点,我决定封装一个类,这个类似单例,叫做KKReactors,从而来简化KVO的使用:
下面罗列了以上5个痛点的解决思路:
1、封装接口,屏蔽无用的content参数,写死option为NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld,只留下最少的参数
2、不要使用target-action模式,借鉴Rxswift思想,使用响应式编程,让Block来响应
3、KKReactors内部已经实现了对回调的区分,调用者只需要响应Block即可,一个block只响应一个KVO
4、告诉KKReactors,我们的观察工作将会伴随着companion的释放而自动结束,无需手动释放KVO
5、我们可以给KKReactors安装过滤器,过滤器提供了上次变动的时间戳,上次调用的时间戳,用面向函数编程的思想,让调用者自己编程实现对响应的过滤
KKReactors承接了所有KVO的回调,这种回调还是以target-action方式进行的,然后KKReactors根据content参数来区分具体是哪个KVO活动,然后KKReactors再对相应的KVO活动进行block方式的回调。
回调时如果发现companion参数已经为空,就不再进行block方式的回调,而是对KVO活动进行销毁
技术亮点
1、借鉴了Rxswift里disposeBag的思想,disposeBag回收的时候自动释放资源。我们的KVO也会随着companion的释放而自动释放
2、使用了函数式编程。对时间的过滤不是采用过滤的选项,而是开放了过滤函数及充足的参数,让用户之地你故意过滤规则,更自由、更灵活
3、返回值是KKWatching类型的,它的本质是: id,它是一种协议,而不是简单的对象,协议里提供了释放对方的两种方法,但不提供对象内部实现细节,内部尽可能少地对外暴露实现细节,体现了六大设计原则里的依赖倒置原则,又充分体现了面向协议编程的思想
4、用Block的方式,返回2个值,解决了OC不支持返回多个返回值的缺陷
代码
头文件
//
// KKReactors.h
// KKReactors
//
// Created by 龚成诗 on 2020/3/22.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#pragma mark - KKBaseReaction declaration
@protocol KKBaseReaction <NSObject>//基础协议
@optional
//立刻销毁这个reactor活动
- (void)disposeNow;
///设置本reactor与companion同寿,内部对companion是弱引用,companion被置空后,这个reactor自动销毁
- (void)persistWithCompanion:(NSObject*)companion;
@end
#pragma mark - KKWatchReaction declaration
//KVO(watch)接口返回值类型,返回值能提供给外部调用的接口都在协议里,含基类协议
@protocol KKWatchReaction <KKBaseReaction>
@optional
///给KVO的回调加装过滤器,传空表示移除过滤器
- (void)installFilter:(BOOL (^ __nullable)(NSInteger counter,//返回YES表示不过滤(需要执行),返回NO表示抛弃这次回调
NSTimeInterval responserInterval,//上次对responser的调用距离现在多久
NSTimeInterval variationInterval,//上次值变动距离现在多久
NSObject* target,//观察对象
NSString* keyPath,//观察变量、路径
NSObject* __nullable oldValue,
NSObject* __nullable newValue))filter;
@end
///用KKWatching简化KVO(watch)接口返回值类型
#define KKWatching id<KKWatchReaction>
//notification(listen)接口返回值,返回值能提供给外部调用的接口都在协议里,含基类协议
@protocol KKListenReaction <KKBaseReaction>
@optional
///给Notification的回调加装过滤器,传空表示移除过滤器
- (void)installFilter:(BOOL (^ __nullable)(NSInteger counter,//返回YES表示不过滤,需要执行,返回NO表示抛弃这次回调
NSTimeInterval responserInterval,//上次对responser的调用距离现在多久
NSTimeInterval notificationInterval,//上次发通知距离现在多久
NSString* name))filter;
@end
///用KKListening简化notification(listen)接口返回值类型
#define KKListening id<KKListenReaction>
//消息订阅(subscribe)接口返回值,返回值能提供给外部调用的接口都在协议里,含基类协议
@protocol KKSubscribeReaction <KKBaseReaction>
@optional
///给消息订阅的回调加装过滤器,传空表示移除过滤器
- (void)installFilter:(BOOL (^ __nullable)(NSInteger counter,//返回YES表示不过滤,需要执行,返回NO表示抛弃这次回调
NSTimeInterval responserInterval,//上次对responser的调用距离现在多久
NSTimeInterval postingInterval,//上次发送message距离现在多久
NSString* mesage,
id parameter1,
id parameter2,
id parameter3,
id parameter4))filter;
@end
///用KKSubscribing简化消息订阅(subscribe)接口返回值类型
#define KKSubscribing id<KKSubscribeReaction>
#pragma mark - KKReactors declaration
///主类,单例
@interface KKReactors : NSObject
///单例的简化定义
SINGLETON_H
///观察target的keyPath,发生变化后会触发回调(responser)
- (KKWatching __nullable)watch:(NSObject *)target//观察谁
keyPath:(NSString*)keyPath//观察路径
responser:(void (^ )(NSObject* __nullable oldValue,//观察回调,老值
NSObject* __nullable newValue,//观察回调,新值
NSObject* target,//观察回调,观察对象
NSString* keyPath))responser;//观察回调,观察路径
///添加对通知name的响应(responser)
- (KKListening __nullable)listen:(NSString*)name//通知的名称
responser:(void (^ )(NSString* name,
NSDictionary* __nullable userInfo))responser;//通知的回调,通知名称
///监听键盘出现消失通知,result返回两个参数,弥补了OC不支持多参数返回的缺陷
- (void)listenKeyBoardWithResult:(void (^ )(KKListening __nullable keyboardShowListening, KKListening __nullable keyboardHideListening))result
responser:(void (^ )(CGFloat keyboardHeight))responser;
///添加对自定义消息name的响应(subscribe)
- (KKSubscribing __nullable)subscribe:(NSString*)messgae//消息的名称
responser:(void (^ )(NSString* messgae,//消息的名称
__nullable id parameter1,//参数
__nullable id parameter2,
__nullable id parameter3,
__nullable id parameter4))responser;//消息的响应的回调,通知名称
///发送自定义消息,最多带4个参数
- (void)postMessage:(NSString*)message
parameter1:(__nullable id)parameter1//参数1
parameter2:(__nullable id)parameter2//参数2
parameter3:(__nullable id)parameter3//参数3
parameter4:(__nullable id)parameter4;//参数4
///监听电源键按下消息
- (KKSubscribing __nullable)subscribePowerButtonMessageWithResponser:(void (^ )(NSNumber* screenIsOn))responser;//screenIsOn里面是Bool,True表示屏幕亮着
@end
NS_ASSUME_NONNULL_END
源文件
//
// KKReactors.m
// KKReactors
//
// Created by 龚成诗 on 2020/3/22.
//
#import "KKReactors.h"
#define kPowerButtonDidPressMessageName @"com.apple.iokit.hid.displayStatus"
#pragma mark - KKReactors declaration
@interface KKReactors()
- (void)doDisposing;
@property (nonatomic, strong, class) NSNumber* screenIsOn;
@property (nonatomic, strong, class) NSString* powerButtonMessageName;//按下电源键后,内部自动调用post接口,消息名称就是这个变量
@end
static void powerButtonDidPress(CFNotificationCenterRef center, void* observer, CFStringRef name, const void* object, CFDictionaryRef userInfo)
{
if (KKReactors.powerButtonMessageName && CFEqual(name, (__bridge CFStringRef)KKReactors.powerButtonMessageName))
{
if ([KKReactors.screenIsOn boolValue])
{
KKReactors.screenIsOn = @NO;
}
else
{
KKReactors.screenIsOn = @YES;
}
[KKReactors.sharedInstance postMessage:KKReactors.powerButtonMessageName
parameter1:KKReactors.screenIsOn
parameter2:nil
parameter3:nil
parameter4:nil];
}
}
#pragma mark - KKBaseModel
@interface KKBaseModel : NSObject <KKBaseReaction>
@property (nonatomic, weak) NSObject* companion;
@property (nonatomic, strong, readonly) NSString* stringKey;
@property (nonatomic, weak, readonly) KKReactors* reactors;
- (instancetype)initWithStringKey:(NSString*)stringKey
reactors:(KKReactors*)reactors;
@end
@implementation KKBaseModel
- (instancetype)initWithStringKey:(NSString*)stringKey
reactors:(KKReactors*)reactors
{
self = [super init];
if (self)
{
_stringKey = stringKey;
_reactors = reactors;
}
return self;
}
- (void)disposeNow
{
self.companion = nil;
[_reactors doDisposing];
}
- (void)persistWithCompanion:(NSObject*)companion
{
self.companion = companion;
}
@end
#pragma mark - KKWatchModel
@interface KKWatchModel : KKBaseModel <KKWatchReaction>
@end
@implementation KKWatchModel
{
__weak KKReactors* _reactors;
NSTimeInterval _variationTimeStamp;
NSTimeInterval _responserTimeStamp;
NSInteger _counter;
BOOL (^ __nullable _filter)(NSInteger counter,
NSTimeInterval responserInterval,//上次对responser的调用距离现在多久
NSTimeInterval notificationInterval,//上次值变动距离现在多久
NSObject* target,
NSString* keyPath,
NSObject* __nullable oldValue,
NSObject* __nullable newValue);
void(^ _responser)(NSObject* oldValue, NSObject* newValue, NSObject* target, NSString* keyPath);
}
- (instancetype)initWithTarget:(NSObject*)target
keyPath:(NSString*)keyPath
stringKey:(NSString*)stringKey
reactors:(KKReactors*)reactors
responser:(void (^)(NSObject* oldValue, NSObject* newValue, NSObject* target, NSString* keyPath))responser
{
self = [super initWithStringKey:stringKey reactors:reactors];
if (self)
{
super.companion = target;
_variationTimeStamp = 0.0;
_responserTimeStamp = 0.0;
_counter = 0;
_responser = responser;
}
return self;
}
- (void)installFilter:(BOOL (^ __nullable)(NSInteger counter,
NSTimeInterval responserInterval,//上次对responser的调用距离现在多久
NSTimeInterval notifiyingInterval,//上次通知距离现在多久
NSObject* target,
NSString* keyPath,
NSObject* __nullable oldValue,
NSObject* __nullable newValue))filter
{
_filter = [filter copy];
}
- (void)oldValue:(NSObject*)oldValue
newValue:(NSObject*)newValue
target:(NSObject*)target
keyPath:(NSString*)keyPath
{
_counter++;
NSTimeInterval nowTimeStamp = [[NSDate date] timeIntervalSince1970];
NSTimeInterval variationInteval = (_variationTimeStamp == 0.0 ? 0.0 : (nowTimeStamp - _variationTimeStamp));
NSTimeInterval responserInteval = (_responserTimeStamp == 0.0 ? 0.0 : (nowTimeStamp - _responserTimeStamp));
_variationTimeStamp = nowTimeStamp;
if (_filter
&&
_filter(_counter,
responserInteval,
variationInteval,
target,
keyPath,
oldValue,
newValue) == NO)
{
return;
}
_responserTimeStamp = nowTimeStamp;
_responser(oldValue, newValue, target, keyPath);
}
@end
#pragma mark - KKWatchReactor
@interface KKWatchReactor : NSObject
@property (nonatomic, weak, readonly) NSObject* target;
@property (nonatomic, strong, readonly) NSString* stringKey;
@end
@implementation KKWatchReactor
{
__weak KKReactors* _reactors;
NSString* _keyPath;
NSMutableDictionary<NSString*, KKWatchModel*>* _allWatchModels;
}
- (instancetype)initWithTarget:(NSObject*)target
keyPath:(NSString*)keyPath
stringKey:(NSString*)stringKey
watcher:(KKReactors*)watcher
{
self = [super init];
if (self) {
_target = target;
_keyPath = [keyPath copy];
_stringKey = stringKey;
_reactors = watcher;
[_target addObserver:_reactors
forKeyPath:_keyPath
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
context:(void*)_stringKey.UTF8String];
}
return self;
}
- (void)oldValue:(NSObject*)oldValue
newValue:(NSObject*)newValue
{
[_allWatchModels enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, KKWatchModel * _Nonnull aWatchModel, BOOL * _Nonnull stop) {
[aWatchModel oldValue:oldValue
newValue:newValue
target:_target
keyPath:_keyPath];
}];
}
- (KKWatchModel*)getModelWithResponser:(void (^ )(NSObject* oldValue,
NSObject* newValue,
NSObject* target,
NSString* keyPath))responser
{
if (_allWatchModels == nil)
{
_allWatchModels = [[NSMutableDictionary alloc] init];
}
NSString* modelStringKey = [NSString stringWithFormat:@"%p", responser];
KKWatchModel* aWatchModel = _allWatchModels[modelStringKey];
if (aWatchModel == nil)
{
aWatchModel = [[KKWatchModel alloc] initWithTarget:_target
keyPath:_keyPath
stringKey:modelStringKey
reactors:_reactors
responser:responser];
}
_allWatchModels[aWatchModel.stringKey] = aWatchModel;
return aWatchModel;
}
- (BOOL)doDisposing
{
[_allWatchModels enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, KKWatchModel * _Nonnull aWatchModel, BOOL * _Nonnull stop) {
if (aWatchModel.companion == nil)
{
[_allWatchModels removeObjectForKey:aWatchModel.stringKey];
}
}];
if (_allWatchModels.count == 0)
{
_allWatchModels = nil;
}
if (_target == nil || _allWatchModels == nil)
{
return YES;
}
return NO;
}
- (void)dealloc
{
[_target removeObserver:_reactors
forKeyPath:_keyPath
context:(void*)_stringKey.UTF8String];
}
@end
#pragma mark - KKListenModel
@interface KKListenModel : KKBaseModel <KKListenReaction>
@end
@implementation KKListenModel
{
__weak KKReactors* _reactors;
NSTimeInterval _notificationInterval;
NSTimeInterval _responserTimeStamp;
NSInteger _counter;
BOOL (^ __nullable _filter)(NSInteger counter,
NSTimeInterval responserInterval,//上次对responser的调用距离现在多久
NSTimeInterval notificationInterval,//上次值变动距离现在多久
NSString* name,
NSDictionary* userInfo);
void(^ _responser)(NSString* name, NSDictionary* userInfo);
}
- (instancetype)initWithName:(NSString*)name
stringKey:(NSString*)stringKey
reactors:(KKReactors*)reactors
responser:(void (^)(NSString* name, NSDictionary* userInfo))responser
{
self = [super initWithStringKey:stringKey reactors:reactors];
if (self)
{
super.companion = name;
_notificationInterval = 0.0;
_responserTimeStamp = 0.0;
_counter = 0;
_responser = responser;
}
return self;
}
- (void)installFilter:(BOOL (^ __nullable)(NSInteger counter,
NSTimeInterval responserInterval,//上次对responser的调用距离现在多久
NSTimeInterval notificationInterval,//上次值变动距离现在多久
NSString* name))filter
{
_filter = [filter copy];
}
- (void)onNotification:(NSString*)name
userInfo:(NSDictionary*)userInfo
{
_counter++;
NSTimeInterval nowTimeStamp = [[NSDate date] timeIntervalSince1970];
NSTimeInterval variationInteval = (_notificationInterval == 0.0 ? 0.0 : (nowTimeStamp - _notificationInterval));
NSTimeInterval responserInteval = (_responserTimeStamp == 0.0 ? 0.0 : (nowTimeStamp - _responserTimeStamp));
_notificationInterval = nowTimeStamp;
if (_filter
&&
_filter(_counter,
responserInteval,
variationInteval,
name,
userInfo) == NO)
{
return;
}
_responserTimeStamp = nowTimeStamp;
_responser(name, userInfo);
}
@end
#pragma mark - KKListenReactor
@interface KKListenReactor : NSObject
@property (nonatomic, strong, readonly) NSString* name;
@property (nonatomic, strong, readonly) NSString* stringKey;
@end
@implementation KKListenReactor
{
__weak KKReactors* _reactors;
NSString* _name;
NSMutableDictionary<NSString*, KKListenModel*>* _allListenModels;
}
- (instancetype)initWithName:(NSString*)name
stringKey:(NSString*)stringKey
watcher:(KKReactors*)watcher
notificationAction:(SEL)notificationAction
{
self = [super init];
if (self)
{
_stringKey = stringKey;
_reactors = watcher;
_name = name;
[NSNotificationCenter.defaultCenter addObserver:_reactors
selector:notificationAction
name:name
object:nil];
}
return self;
}
- (void)onNotification:(NSDictionary *)userInfo
{
[_allListenModels enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, KKListenModel * _Nonnull aListenModel, BOOL * _Nonnull stop) {
[aListenModel onNotification:_name userInfo:userInfo];
}];
}
- (KKListenModel*)getModelWithResponser:(void (^ )(NSString* name, NSDictionary* userInfo))responser
{
if (_allListenModels == nil)
{
_allListenModels = [[NSMutableDictionary alloc] init];
}
NSString* modelStringKey = [NSString stringWithFormat:@"%p", responser];
KKListenModel* aListenModel = _allListenModels[modelStringKey];
if (aListenModel == nil)
{
aListenModel = [[KKListenModel alloc] initWithName:_name
stringKey:modelStringKey
reactors:_reactors
responser:responser];
}
_allListenModels[aListenModel.stringKey] = aListenModel;
return aListenModel;
}
- (BOOL)doDisposing
{
[_allListenModels enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, KKListenModel * _Nonnull aListenModel, BOOL * _Nonnull stop) {
if (aListenModel.companion == nil)
{
[_allListenModels removeObjectForKey:aListenModel.stringKey];
}
}];
if (_allListenModels.count == 0)
{
_allListenModels = nil;
}
if (_allListenModels == nil)
{
return YES;
}
return NO;
}
- (void)dealloc
{
[NSNotificationCenter.defaultCenter removeObserver:_reactors
name:_name
object:nil];
}
@end
#pragma mark - KKSubscribeModel
@interface KKSubscribeModel : KKBaseModel <KKSubscribeReaction>
@end
@implementation KKSubscribeModel
{
__weak KKReactors* _reactors;
NSTimeInterval _postingTimeStamp;
NSTimeInterval _responserTimeStamp;
NSInteger _counter;
id _parameter1;
id _parameter2;
id _parameter3;
id _parameter4;
BOOL (^ __nullable _filter)(NSInteger counter,
NSTimeInterval responserInterval,//上次对responser的调用距离现在多久
NSTimeInterval postingInterval,//上次发送message距离现在多久
NSString* message,
id parameter1,
id parameter2,
id parameter3,
id parameter4);
void(^ _responser)(NSString* message,
id parameter1,
id parameter2,
id parameter3,
id parameter4);
}
- (instancetype)initWithMessage:(NSString*)message
stringKey:(NSString*)stringKey
reactors:(KKReactors*)reactors
responser:(void (^)(NSString* message,
id parameter1,
id parameter2,
id parameter3,
id parameter4))responser
{
self = [super initWithStringKey:stringKey reactors:reactors];
if (self)
{
super.companion = message;
_postingTimeStamp = 0.0;
_responserTimeStamp = 0.0;
_counter = 0;
_responser = responser;
}
return self;
}
- (void)installFilter:(BOOL (^ __nullable)(NSInteger counter,
NSTimeInterval responserInterval,//上次对responser的调用距离现在多久
NSTimeInterval postingInterval,//上次发送message距离现在多久
NSString* mesage,
id parameter1,
id parameter2,
id parameter3,
id parameter4))filter
{
_filter = [filter copy];
}
- (void)onMessage:(NSString*)message
parameter1:(id)parameter1
parameter2:(id)parameter2
parameter3:(id)parameter3
parameter4:(id)parameter4
{
_counter++;
NSTimeInterval nowTimeStamp = [[NSDate date] timeIntervalSince1970];
NSTimeInterval postingInteval = (_postingTimeStamp == 0.0 ? 0.0 : (nowTimeStamp - _postingTimeStamp));
NSTimeInterval responserInteval = (_responserTimeStamp == 0.0 ? 0.0 : (nowTimeStamp - _responserTimeStamp));
_postingTimeStamp = nowTimeStamp;
if (_filter
&&
_filter(_counter,
responserInteval,
postingInteval,
message,
parameter1,
parameter2,
parameter2,
parameter4) == NO)
{
return;
}
_responserTimeStamp = nowTimeStamp;
_responser(message,
parameter1,
parameter2,
parameter3,
parameter4);
}
@end
#pragma mark - KKSubscribeReactor
@interface KKSubscribeReactor : NSObject
@property (nonatomic, strong, readonly) NSString* message;
@property (nonatomic, strong, readonly) NSString* stringKey;
@end
@implementation KKSubscribeReactor
{
__weak KKReactors* _reactors;
NSString* _message;
NSMutableDictionary<NSString*, KKSubscribeModel*>* _allSubscribeModels;
}
- (instancetype)initWithMessage:(NSString*)message
stringKey:(NSString*)stringKey
watcher:(KKReactors*)watcher
{
self = [super init];
if (self)
{
_stringKey = stringKey;
_reactors = watcher;
_message = message;
}
return self;
}
- (void)parameter1:(id)parameter1
parameter2:(id)parameter2
Parameter3:(id)parameter3
Parameter4:(id)parameter4
{
[_allSubscribeModels enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, KKSubscribeModel * _Nonnull aListenModel, BOOL * _Nonnull stop) {
[aListenModel onMessage:_message
parameter1:parameter1
parameter2:parameter2
parameter3:parameter3
parameter4:parameter4];
}];
}
- (KKSubscribeModel*)getModelWithResponser:(void (^ )(NSString* message,
id parameter1,
id parameter2,
id parameter3,
id parameter4))responser
{
if (_allSubscribeModels == nil)
{
_allSubscribeModels = [[NSMutableDictionary alloc] init];
}
NSString* modelStringKey = [NSString stringWithFormat:@"%p", responser];
NSLog(@"block = %@", modelStringKey);
KKSubscribeModel* aSubscribeModel = _allSubscribeModels[modelStringKey];
if (aSubscribeModel == nil)
{
aSubscribeModel = [[KKSubscribeModel alloc] initWithMessage:_message
stringKey:modelStringKey
reactors:_reactors
responser:responser];
}
_allSubscribeModels[aSubscribeModel.stringKey] = aSubscribeModel;
return aSubscribeModel;
}
- (BOOL)doDisposing
{
[_allSubscribeModels enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, KKSubscribeModel * _Nonnull aSubscribeModel, BOOL * _Nonnull stop) {
if (aSubscribeModel.companion == nil)
{
[_allSubscribeModels removeObjectForKey:aSubscribeModel.stringKey];
}
}];
if (_allSubscribeModels.count == 0)
{
_allSubscribeModels = nil;
}
if (_allSubscribeModels == nil)
{
return YES;
}
return NO;
}
- (void)dealloc
{
}
@end
#pragma mark - KKReactors
@implementation KKReactors
{
NSMutableDictionary<NSString*, KKWatchReactor*>* _allWatchReactors;//所有的观察活动
NSMutableDictionary<NSString*, KKListenReactor*>* _allListenReactors;//所有通知的监听活动
NSMutableDictionary<NSString*, KKSubscribeReactor*>* _allSubscribeReactors;//所有对消息的订阅活动
}
SINGLETON_M;
static NSNumber* s_screenIsOn;
+ (void)setScreenIsOn:(NSNumber *)screenIsOn
{
s_screenIsOn = screenIsOn;
}
+ (NSNumber*)screenIsOn
{
return s_screenIsOn;
}
static NSString* s_powerButtonMessageName;
+ (void)setPowerButtonMessageName:(NSString *)powerButtonMessageName
{
s_powerButtonMessageName = powerButtonMessageName;
}
+ (NSString*)powerButtonMessageName
{
return s_powerButtonMessageName;
}
- (KKWatchReactor*)getWatchReactorWithTarget:(NSObject*)target
keyPath:(NSString*)keyPath
{
if (_allWatchReactors == nil)
{
_allWatchReactors = [[NSMutableDictionary alloc] init];
}
NSString* reactorStringKey = [NSString stringWithFormat:@"%p-%@", target, keyPath];
KKWatchReactor* aWatchReactor = _allWatchReactors[reactorStringKey];
if (aWatchReactor == nil)
{
aWatchReactor = [[KKWatchReactor alloc] initWithTarget:target
keyPath:keyPath
stringKey:reactorStringKey
watcher:self];
}
_allWatchReactors[aWatchReactor.stringKey] = aWatchReactor;
return aWatchReactor;
}
- (KKWatching __nullable)watch:(NSObject *)target
keyPath:(NSString*)keyPath
responser:(void (^ )(NSObject* oldValue,
NSObject* newValue,
NSObject* target,
NSString* keyPath))responser
{
[self doDisposing];
if (target == nil || keyPath.length == 0 || responser == nil)
{
return nil;
}
KKWatchReactor* aWatchReactor = [self getWatchReactorWithTarget:target
keyPath:keyPath];
KKWatchModel* aWatchModel = [aWatchReactor getModelWithResponser:[responser copy]];
return aWatchModel;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
[self doDisposing];
char* chReactorStringKey = (char*)context;
NSString* reactorStringKey = [NSString stringWithUTF8String:chReactorStringKey];
KKWatchReactor* aWatchReactor = _allWatchReactors[reactorStringKey];
if (aWatchReactor == nil)
{
return;
}
[aWatchReactor oldValue:change[@"old"] newValue:change[@"new"]];
}
- (KKListenReactor*)getListenReactorWithName:(NSString*)name
{
if (_allListenReactors == nil)
{
_allListenReactors = [[NSMutableDictionary alloc] init];
}
NSString* reactorStringKey = name;
KKListenReactor* aListenReactor = _allListenReactors[reactorStringKey];
if (aListenReactor == nil)
{
aListenReactor = [[KKListenReactor alloc] initWithName:name
stringKey:reactorStringKey
watcher:self
notificationAction:@selector(onNotification:)];
}
_allListenReactors[aListenReactor.stringKey] = aListenReactor;
return aListenReactor;
}
- (KKListening __nullable)listen:(NSString*)name//通知的名称
responser:(void (^ )(NSString* name, NSDictionary* userInfo))responser//通知的回调,通知名称
{
[self doDisposing];
if (name.length == 0 || responser == nil)
{
return nil;
}
KKListenReactor* aListenReactor = [self getListenReactorWithName:name];
KKListenModel* aListenModel = [aListenReactor getModelWithResponser:[responser copy]];
return aListenModel;
}
- (void)onNotification:(NSNotification*)notification
{
[self doDisposing];
NSString* name = notification.name;
NSDictionary* userInfo = notification.userInfo;
if (name.length == 0)
{
return;
}
[_allListenReactors[name] onNotification:userInfo];
}
///监听键盘出现消失通知
- (void)listenKeyBoardWithResult:(void (^ )(KKListening keyboardShowListening, KKListening keyboardHideListening))result
responser:(void (^ )(CGFloat keyboardHeight))responser
{
if (result == nil)
{
return;
}
KKListening keyboardShowListening = [self listen:UIKeyboardWillShowNotification responser:^(NSString * _Nonnull name, NSDictionary * _Nullable userInfo) {
CGRect keyboardRect = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
responser(keyboardRect.size.height);
}];
KKListening keyboardHideListening = [self listen:UIKeyboardWillHideNotification responser:^(NSString * _Nonnull name, NSDictionary * _Nullable userInfo) {
responser(0.0f);
}];
result(keyboardShowListening, keyboardHideListening);
}
- (KKSubscribeReactor*)getSubscribeReactorWithName:(NSString*)message
{
if (_allSubscribeReactors == nil)
{
_allSubscribeReactors = [[NSMutableDictionary alloc] init];
}
NSString* reactorStringKey = message;
KKSubscribeReactor* aSubscribeReactor = _allSubscribeReactors[reactorStringKey];
if (aSubscribeReactor == nil)
{
aSubscribeReactor = [[KKSubscribeReactor alloc] initWithMessage:message
stringKey:reactorStringKey
watcher:self];
}
_allSubscribeReactors[aSubscribeReactor.stringKey] = aSubscribeReactor;
return aSubscribeReactor;
}
///添加对自定义消息name的响应(subscribe)
- (KKSubscribing __nullable)subscribe:(NSString*)messgae//消息的名称
responser:(void (^ )(NSString* messgae,//消息的名称
id parameter1,//参数
id parameter2,
id parameter3,
id parameter4))responser//消息的响应的回调,通知名称
{
[self doDisposing];
if (messgae.length == 0 || responser == nil)
{
return nil;
}
KKSubscribeReactor* aSubscribeReactor = [self getSubscribeReactorWithName:messgae];
KKSubscribeModel* aSubscribeModel = [aSubscribeReactor getModelWithResponser:[responser copy]];
return aSubscribeModel;
}
- (void)postMessage:(NSString*)message
parameter1:(id)parameter1//参数1
parameter2:(id)parameter2//参数2
parameter3:(id)parameter3//参数3
parameter4:(id)parameter4//参数4
{
[self doDisposing];
if (message.length == 0)
{
return;
}
[_allSubscribeReactors[message] parameter1:parameter1
parameter2:parameter2
Parameter3:parameter3
Parameter4:parameter4];
}
- (void)doDisposing
{
[_allWatchReactors enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, KKWatchReactor * _Nonnull aWatchReactor, BOOL * _Nonnull stop) {
if ([aWatchReactor doDisposing])
{
[_allWatchReactors removeObjectForKey:aWatchReactor.stringKey];
}
}];
if (_allWatchReactors.count == 0)
{
_allWatchReactors = nil;
}
[_allListenReactors enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, KKListenReactor * _Nonnull aListenReactor, BOOL * _Nonnull stop) {
if ([aListenReactor doDisposing])
{
[_allListenReactors removeObjectForKey:aListenReactor.stringKey];
}
}];
if (_allListenReactors.count == 0)
{
_allListenReactors = nil;
}
[_allSubscribeReactors enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, KKSubscribeReactor * _Nonnull aSubscribeReactor, BOOL * _Nonnull stop) {
if ([aSubscribeReactor doDisposing])
{
[_allSubscribeReactors removeObjectForKey:aSubscribeReactor.stringKey];
}
}];
if (_allSubscribeReactors.count == 0)
{
_allSubscribeReactors = nil;
}
}
///监听电源键按下消息
- (KKSubscribing __nullable)subscribePowerButtonMessageWithResponser:(void (^ )(NSNumber* screenIsOn))responser//screenIsOn里面是Bool,True表示屏幕亮着
{
if (responser == nil)
{
return nil;
}
if (KKReactors.powerButtonMessageName == nil)
{
KKReactors.screenIsOn = @YES;
KKReactors.powerButtonMessageName = kPowerButtonDidPressMessageName;
//注册锁屏通知
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
NULL,
powerButtonDidPress,
(__bridge CFStringRef)KKReactors.powerButtonMessageName,
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
}
return [self subscribe:KKReactors.powerButtonMessageName
responser:^(NSString * _Nonnull messgae, id _Nullable parameter1, id _Nullable parameter2, id _Nullable parameter3, id _Nullable parameter4) {
responser(parameter1);
}];
}
@end
调用举例
Person* _person = Person.new;
KKReactors watching =
[KKReactors.sharedInstance watchTarget:_person
atPath:@"name"
responser:^(NSObject * __nullable oldValue, NSObject * __nullable newValue, NSObject * _Nonnull target, NSString * _Nonnull keyPath) {
NSLog(@"%@ %@", oldValue, newValue);
}];
[watching installFilter:^BOOL(NSInteger counter,
NSTimeInterval responserInterval,
NSTimeInterval variationInterval,
NSObject* target,
NSString* keyPath,
NSObject * _Nullable oldValue,
NSObject * _Nullable newValue) {
if (counter < 2)//只需要响应两次
{
return YES;
}
return NO;
}];
_person.name = @"abc";
_person.name = @"def";
_person.name = @"ghi";
[watching persistWithCompanion:self];//当前VC释放后,KVO跟着一起释放
特别说明
1、代码中涉及到的SINGLETON_H和SINGLETON_M两个宏,请参考我的前面一篇博客。
2、block里面引用到self,一定要weak化,建议使用我上篇博客里提到的WEAK和STRONG宏
3、代码中运用同样的思路,也改造了NSNotification,使得NSNotification也支持函数式调用,参考代码中的 listen:reponsor:接口
4、代码中运用同样的思路,支持自定义小的发送和接受,参考subscribe接口和postMessage接口
5、代码中还封装了对键盘出现消失通知的封装,参考listenKeyBoardWithResult接口。这个接口用Block的方式,返回2个值,解决了OC不支持返回多个返回值的缺陷