iOS基于KVO实现响应式编程之完结篇
Posted JackLee18
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS基于KVO实现响应式编程之完结篇相关的知识,希望对你有一定的参考价值。
最近一直在探索基于KVO实现响应式编程,之前也写了两篇相关的文章。《OC实现KVO监听block方式响应事件》,《iOS 通过KVO实现响应式编程(一)》最近方案基本完善。这边完整的和大家梳理一下。
需求梳理
一、监听非数组对象的属性变化
二、监听数组数据的变化
1)监听数组指针的变化
2)监听数组元素的变化
a、监听数组元素数量以及元素顺序的变化
b、监听数组数组元素对应的属性的变化
能够将上面所列的变化,以及详细信息通过回调的形式告知使用者。
意义
通过实现对数据变化的监听,我们就可以实现基于数据驱动的UI交互,事件交互。网络请求交互。逻辑更加的清晰,代码更好的维护;同时更好的实现局部刷新,提高app的性能。对于大型业务复杂的app更加适合。
技术实现
技术实现主要从以下几个方面进行考虑和实现
较低学习成本
为了降低学习成本,这边主要采取类似原生kvo添加监听的形式,监听的变化通过回调的形式触发。示例如下:
/**
添加keyPath监听,有context
@param observer 观察者
@param keyPath keyPath
@param options options
@param context context
@param block 回调
*/
- (void)jk_addObserver:(__kindof NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
withBlock:(void(^)(NSDictionary *change, void *context))block;
避免干扰其他代码
实现监听的真正的观察者并不是从外部传入的,而是以外部传入的observer作为标记的一个JKKVOObserver对象,避免拦截外部传入的observer相关方法,造成项目中使用原生kvo产生问题。
对于数组中添加,删除,替换元素的方法,并没有直接的进行hook,而是在分类里添加了如下方法:
- (void)kvo_addObject:(id)anObject;
- (void)kvo_insertObject:(id)anObject atIndex:(NSUInteger)index;
- (void)kvo_removeLastObject;
- (void)kvo_removeObjectAtIndex:(NSUInteger)index;
- (void)kvo_replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;
- (void)kvo_addObjectsFromArray:(NSArray<id> *)otherArray;
- (void)kvo_exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2;
- (void)kvo_removeAllObjects;
- (void)kvo_removeObject:(id)anObject;
内部实现监听相关的操作,避免干扰到原生的方法,造成性能问题 PS:添加监听后,使用kvo前缀的方法操作数组里的元素,就能够捕获到数组的变化
较低的升级维护成本
关于监听相关的方法,以及数组转化响应式的操作已经完成,后续升级也只是底层方面的性能优化,开发使用的方法不会有太大的变化,因此后续升级维护的成本较低。
主要的类
JKKVOObserver
@interface JKKVOObserver : NSObject
@property (nonatomic, weak, nullable, readonly) __kindof NSObject *originObserver;
@property (nonatomic, copy, nullable, readonly) NSString *originObserver_address;
@property (nonatomic, assign) NSUInteger observerCount;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)initWithOriginObserver:(__kindof NSObject *)originObserver;
@end
JKKVOObserver对象是框架真正的观察者。originObserver 开发者外部传入的观察者,originObserver_address开发者外部传入的观察者的内存地址。
内部实现如下:
+ (void)load
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
Class class = [JKKVOObserver class];
SEL observeValueForKeyPath = @selector(observeValueForKeyPath:ofObject:change:context:);
SEL jk_ObserveValueForKeyPath = @selector(jkhook_observeValueForKeyPath:ofObject:change:context:);
[JKKVOItemManager jk_exchangeInstanceMethod:class originalSel:observeValueForKeyPath swizzledSel:jk_ObserveValueForKeyPath];
);
+ (instancetype)initWithOriginObserver:(__kindof NSObject *)originObserver
JKKVOObserver *kvoObserver = [[self alloc] init];
if (kvoObserver)
kvoObserver.originObserver = originObserver;
kvoObserver.originObserver_address = [NSString stringWithFormat:@"%p",originObserver];
kvoObserver.observerCount = 1;
return kvoObserver;
- (void)jkhook_observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context
if ([object isKindOfClass:[NSObject class]])
JKKVOItem *item = [JKKVOItemManager isContainItemWith_kvoObserver:self];
if (!item
|| !item.valid)
return;
if ([item isKindOfClass:[JKKVOArrayItem class]])
JKKVOArrayItem *arrayItem = (JKKVOArrayItem *)item;
NSObject *observeredObject = (NSObject *)object;
if ([arrayItem.observered isEqual:observeredObject]) // 数组指针的变化
arrayItem.observered_property = [observeredObject valueForKeyPath:keyPath];
if (arrayItem.observered_property)
NSAssert([arrayItem.observered_property isKindOfClass:[NSArray class]], @"make sure [arrayItem.observered_property isKindOfClass:[NSArray class]] be YES");
void(^detailBlock)(NSString *keyPath, NSDictionary *change, JKKVOArrayChangeModel *changedModel, void *context) = arrayItem.detailBlock;
if (detailBlock)
detailBlock(keyPath,change,nil,context);
else // 数组元素对应的属性的变化
void(^detailBlock)(NSString *keyPath, NSDictionary *change, JKKVOArrayChangeModel *changedModel, void *context) = arrayItem.detailBlock;
if (detailBlock)
NSArray <JKKVOArrayElement *>*kvoElements = [arrayItem kvoElementsWithElement:observeredObject];
JKKVOArrayChangeModel *changedModel = [JKKVOArrayChangeModel new];
changedModel.changeType = JKKVOArrayChangeTypeElement;
changedModel.changedElements = kvoElements;
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
if ((arrayItem.options & NSKeyValueObservingOptionOld) == NSKeyValueObservingOptionOld)
dic[NSKeyValueChangeOldKey] = arrayItem.observered_property;
if ((arrayItem.options & NSKeyValueObservingOptionNew) == NSKeyValueObservingOptionNew)
dic[NSKeyValueChangeNewKey] = arrayItem.observered_property;
detailBlock(arrayItem.keyPath,dic,changedModel,arrayItem.context);
else
void(^block)(NSString *keyPath, NSDictionary *change, void *context) = item.block;
if (block)
block(keyPath,change,context);
可以看到对observeValueForKeyPath:ofObject:change:context:
这个方法进行了拦截,不会干扰到外部的类,通过回调的形式,将变化传递到业务层。
JKKVOItem
@interface JKKVOItem : NSObject
/// 观察者
@property (nonatomic, strong, nonnull, readonly) JKKVOObserver *kvoObserver;
/// 被观察者
@property (nonatomic, weak, nullable, readonly) __kindof NSObject *observered;
///被观察者的内存地址
@property (nonatomic, copy, nullable, readonly) NSString *observered_address;
/// 监听的keyPath
@property (nonatomic, copy, nonnull, readonly) NSString *keyPath;
/// 上下文
@property (nonatomic, nullable, readonly) void *context;
/// 回调
@property (nonatomic, copy, readonly) void(^block)(NSString *keyPath, NSDictionary *change, void *context);
/// 是否有效
@property (nonatomic, assign, readonly) BOOL valid;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
/// 非数组的监听
+ (instancetype)initWith_kvoObserver:(nonnull JKKVOObserver *)kvoObserver
observered:(nonnull __kindof NSObject *)observered
keyPath:(nonnull NSString *)keyPath
context:(nullable void *)context
block:(nullable void(^)(NSString *keyPath, NSDictionary *change, void *context))block;
@end
每一个非数组对象添加一个监听,就会创建一个JKKVOItem对象,被监听对象释放时,自动移除这个JKKVOItem对象。这些创建的JKKVOItem对象存储在一个全局数组中。
JKKVOArrayItem
如果一个对象被监听的属性是一个数组,那么就会创建JKKVOArrayItem,JKKVOArrayItem继承自JKKVOItem。一个JKKVOArrayItem对象完整的记录了一个一个数组变化的详细信息
@interface JKKVOArrayItem : JKKVOItem
/// 被监听的属性对应的对象
@property (nonatomic, weak, nullable, readonly) __kindof NSArray *observered_property;
///监听选项
@property (nonatomic, assign, readonly) NSKeyValueObservingOptions options;
/// 数组元素需要监听的keyPath的数组
@property (nonatomic, strong, nullable, readonly) NSArray *elementKeyPaths;
/// 被监听的元素map key:element value: 添加监听的次数
@property (nonatomic, strong, nonnull, readonly) NSMapTable *observered_elementMap;
/// 回调
@property (nonatomic, copy, readonly) void(^detailBlock)(NSString *keyPath, NSDictionary *change, JKKVOArrayChangeModel *changedModel, void *context);
+ (instancetype)initWith_kvoObserver:(nonnull JKKVOObserver *)kvoObserver
observered:(nonnull __kindof NSObject *)observered
keyPath:(nonnull NSString *)keyPath
context:(nullable void *)context
options:(NSKeyValueObservingOptions)options
observered_property:(nullable __kindof NSObject *)observered_property
elementKeyPaths:(nullable NSArray *)elementKeyPaths
detailBlock:(nullable void(^)(NSString *keyPath, NSDictionary *change, JKKVOArrayChangeModel *changedModel, void *context))detailBlock;
/// 为数组中的元素添加指定的监听
/// @param element 数组元素
- (void)addObserverOfElement:(nonnull __kindof NSObject *)element;
/// 为数组中的元素移除指定的监听
/// @param element 数组元素
- (void)removeObserverOfElement:(nonnull __kindof NSObject *)element;
- (nullable NSArray <JKKVOArrayElement *>*)kvoElementsWithElement:(nonnull __kindof NSObject *)element;
@end
下面针对JKKVOArrayItem中几个属性进行详细的说明。
@property (nonatomic, weak, nullable, readonly) __kindof NSArray *observered_property;
observered_property 是被监听对象的属性只能是数组类型,如果observered_property 不为空,是非数组类型的话会直接触发断言崩溃掉
@property (nonatomic, strong, nullable, readonly) NSArray *elementKeyPaths;
这个是数组内的元素被监听的keyPath组成的数组,如果为nil那么数组内元素的变化不会触发回调,如果不为空的话,数组内元素会添加相应的监听。
/// 回调
@property (nonatomic, copy, readonly) void(^detailBlock)(NSString *keyPath, NSDictionary *change, JKKVOArrayChangeModel *changedModel, void *context);
keyPath是数组作为某一个对象的属性的keyPath,change 里面对应的是数组变化前后的指针。
changedModel是数组元素数量变化的详细信息
JKKVOArrayChangeType
typedef NS_ENUM(NSInteger,JKKVOArrayChangeType)
/// 缺省值 没有任何改变
JKKVOArrayChangeTypeNone = 0,
/// 根据index增加元素
JKKVOArrayChangeTypeAddAtIndex,
/// 尾部增加元素
JKKVOArrayChangeTypeAddTail,
/// 根据index移除元素
JKKVOArrayChangeTypeRemoveAtIndex,
/// 移除尾部元素
JKKVOArrayChangeTypeRemoveTail,
/// 替换元素
JKKVOArrayChangeTypeReplace,
/// 元素内容改变,指针不变
JKKVOArrayChangeTypeElement,
;
JKKVOArrayChangeType 是数组内元素变化的类型
JKKVOArrayElement
@interface JKKVOArrayElement : NSObject
@property (nonatomic, strong, nonnull, readonly) NSObject *object;
@property (nonatomic, assign, readonly) NSInteger oldIndex;
@property (nonatomic, assign, readonly) NSInteger newIndex;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)elementWithObject:(__kindof NSObject *)object
oldIndex:(NSInteger)oldIndex
newIndex:(NSInteger)newIndex;
@end
这个类主要用来存贮数组元素变化的详细信息的,object就是对应的数组元素,oldIndex是数组元素变化前的索引,newIndex是数组元素变化后的索引
JKKVOArrayChangeModel
@interface JKKVOArrayChangeModel : NSObject
@property (nonatomic, assign) JKKVOArrayChangeType changeType;
@property (nonatomic, strong) NSArray <JKKVOArrayElement *>*changedElements;
@end
这个类主要是用在监听变化的block时返回值,用来记录数组元素变化的详细信息的。changeType表示这次变化的类型,changedElements表示监听到的这次变化有哪些元素直接发生了改变。
使用教程
import <Foundation/Foundation.h>
#import "JKKVOItem.h"
#import "JKKVOItemManager.h"
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (JKKVOHelper)
/**
添加keyPath监听
@param observer 观察者
@param keyPath keyPath
@param options options
@param block 回调
*/
- (void)jk_addObserver:(__kindof NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
withBlock:(void(^)(NSDictionary *change, void *context))block;
/**
添加keyPath监听,有context
@param observer 观察者
@param keyPath keyPath
@param options options
@param context context
@param block 回调
*/
- (void)jk_addObserver:(__kindof NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
withBlock:(void(^)(NSDictionary *change, void *context))block;
/// 添加一组keyPath监听,有context
/// @param observer 观察者
/// @param keyPaths keyPath数组
/// @param options options
/// @param context context
/// @param detailBlock 回调
- (void)jk_addObserver:(__kindof NSObject *)observer
forKeyPaths:(NSArray <NSString *>*)keyPaths
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
withDetailBlock:(void(^)(NSString *keyPath, NSDictionary *change, void *context))detailBlock;
/**
添加keyPath监听,观察者是自己
@param keyPath keyPath
@param options options
@param block 回调
*/
- (void)jk_addObserverForKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
withBlock:(void(^)(NSDictionary *change, void *context))block;
/**
添加keyPath监听,观察者是自己,有context
@param keyPath keyPath
@param options options
@param context context
@param block 回调
*/
- (void)jk_addObserverForKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
withBlock:(void(^)(NSDictionary *change, void *context))block;
/// 添加一组keyPath监听,观察者是自己,有context
/// @param keyPaths keyPath数组
/// @param options options
/// @param context context
/// @param detailBlock 回调
- (void)jk_addObserverForKeyPaths:(NSArray <NSString *>*)keyPaths
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
withDetailBlock:(void(^)(NSString *keyPath, NSDictionary *change, void *context))detailBlock;
/// 监听数组的变化,不监听数组元素属性的变化
/// @param keyPath keyPath,keyPath对应的属性是数组
/// @param options options
/// @param context context
/// @param block block
- (void)jk_addObserverOfArrayForKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
withBlock:(void (^)(NSString *keyPath, NSDictionary *change, JKKVOArrayChangeModel *changedModel, void *context))block;
/// 监听数组的变化,同时监听数组元素属性的变化,观察者是自己
/// @param keyPath keyPath,keyPath对应的属性是数组
/// @param options options
/// @param context context
/// @param elementKeyPaths elementKeyPaths 数组的元素对应的keypath组成的数组
/// @param block block
- (void)jk_addObserverOfArrayForKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
elementKeyPaths:(nullable NSArray <NSString *>*)elementKeyPaths
withBlock:(void (^)(NSString *keyPath, NSDictionary *change, JKKVOArrayChangeModel *changedModel, void *context))block;
/// 监听数组的变化,同时监听数组元素属性的变化
/// @param observer 观察者
/// @param keyPath keyPath keyPath,keyPath对应的属性是数组
/// @param options options
/// @param context context
/// @param elementKeyPaths elementKeyPaths 数组的元素对应的keypath组成的数组
/// @param block block
- (void)jk_addObserverOfArray:(__kindof NSObject *)observer
keyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
elementKeyPaths:(nullable NSArray <NSString *>*)elementKeyPaths
withBlock:(void (^)(NSString *keyPath, NSDictionary *change, JKKVOArrayChangeModel *changedModel, void *context))block;
/**
移除keyPath监听
@param observer 观察者
@param keyPath keyPath
*/
- (void)jk_removeObserver:(__kindof NSObject *)observer
forKeyPath:(NSString *)keyPath;
/// 移除keyPath监听,有context
/// @param observer 观察者
/// @param keyPath keyPath
/// @param context context
- (void)jk_removeObserver:(__kindof NSObject *)observer
forKeyPath:(NSString *)keyPath
context:(nullable void *)context;
/**
移除某个observer下的对应的keyPath列表监听
@param observer 观察者
@param keyPaths keyPath组成的数组
*/
- (void)jk_removeObserver:(__kindof NSObject *)observer
forKeyPaths:(NSArray <NSString *>*)keyPaths;
/**
移除某个keyPath的obsevers对应的监听
如果observers为nil,那么remove掉某个keyPath的所有obsevers对应的监听
@param observers 观察者数组
@param keyPath keyPath
*/
- (void)jk_removeObservers:(NSArray <__kindof NSObject *>*)observers
forKeyPath:(NSString *)keyPath;
/**
移除所有通过jk_前缀添加的观察者,默认在被观察的对象dealloc的时候调用
*/
- (void)jk_removeObservers;
/**
所有的被监听的keyPath列表
@return 被监听的keyPath组成的列表
*/
- (NSArray <NSString *>*)jk_observeredKeyPaths;
/// 监听某一个属性的所有监听者的列表
/// @param keyPath keyPath
- (NSArray <NSObject *>*)jk_observersOfKeyPath:(NSString *)keyPath;
/**
某个观察者监听的keyPath组成的列表
@param observer 观察者
@return keyPath组成的列表
*/
- (NSArray <NSString *>*)jk_keyPathsObserveredBy:(__kindof NSObject *)observer;
@end
NS_ASSUME_NONNULL_END
遇到的坑点
内存陷阱问题
大家可以看到被观察者,我用了两个属性来处理。主要是因为只有weak,才可以让被观察者正常释放,但是执行到dealloc的时候JKKVOItem对象的obsevered属性就为nil了,此时被观察者其实还未释放,此时只能通过比较内存地址来找到为被观察的属性添加的监听,将这些监听都移除掉。
hook dealloc问题
框架中hook dealloc 的代码如下
- (void)jkhook_dealloc
if (![self is_jk_dealloced])
[self setIs_jk_dealloced:YES];
if ([self is_jk_observered])
[self setIs_jk_observered:NO];
[self jk_dealloc_removeObservers];
[self jkhook_dealloc];
else
[self jkhook_dealloc];
如果直接hook了dealloc方法,由于ARC在编译阶段会插入代码 [super dealloc];那么替换的方法会出现循环调用的情况,主要是因为 NSObject 的super指向了自身,系统内部有进行处理,而我们hook 了dealloc方法,也应该进行相应的处理,这边处理的方式是打上标记,避免循环调用。参考博客地址:http://www.chinaoc.com.cn/p/1111398.html
待优化项
由于这个框架底层使用了系统的KVO进行变化的监听,对于一个数组中元素的属性发生变化的情况,如果数组的元素数量过多,那么会添加过多监听,性能变差。后续会考虑自定义实现底层的监听。
单元测试例子
为了充分自测自己的框架,这边我写了一些单元测试样例,供大家参考,示例如下:
#import <Kiwi/Kiwi.h>
#import <JKKVOHelper/JKKVOHelper.h>
#import <JKKVOHelper/JKKVOItemManager.h>
#import "JKTeacher.h"
#import "JKWorker.h"
SPEC_BEGIN(JKKVOHelperSpec)
describe(@"JKKVOHelper", ^
context(@"addObserver", ^
afterEach(^
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array)
[JKKVOItemManager removeItem:item];
);
it(@"addObserver", ^
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
__block BOOL invoked1 = NO;
[worker jk_addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
[[[change objectForKey:@"new"] should] equal:@"zhangsan"];
invoked1 = YES;
];
worker.name = @"zhangsan";
NSArray *array = [JKKVOItemManager items];
[[array should] haveCountOf:1];
[[theValue(invoked1) shouldEventually] beYes];
);
it(@"A observe B, B observe A", ^
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
[worker jk_addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
[person jk_addObserver:worker forKeyPath:@"age" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
NSArray *array = [JKKVOItemManager items];
[[array should] haveCountOf:2];
);
it(@"test observerKeyPaths", ^
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
__block NSInteger invokedCout = 0;
[worker jk_addObserver:person forKeyPaths:@[@"name",@"factory"] options:NSKeyValueObservingOptionNew context:nil withDetailBlock:^(NSString * _Nonnull keyPath, NSDictionary * _Nonnull change, void * _Nonnull context)
if([keyPath isEqualToString:@"name"])
[[[change objectForKey:@"new"] should] equal:@"bbb"];
else if ([keyPath isEqualToString:@"factory"])
JKFactory *factory = [change objectForKey:@"new"];
[[factory.name should] equal:@"China"];
invokedCout++;
];
worker.name = @"bbb";
JKFactory *factory = [JKFactory new];
factory.name = @"China";
worker.factory = factory;
[[theValue(invokedCout) shouldEventually] equal:@(2)];
);
it(@"test observer and observered are the same object", ^
JKPersonModel *person = [JKPersonModel new];
__block BOOL invoked1 = NO;
[person jk_addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
[[[change objectForKey:@"new"] should] equal:@"zhangsan"];
invoked1 = YES;
];
person.name = @"zhangsan";
NSArray *array = [JKKVOItemManager items];
[[array should] haveCountOf:1];
[[theValue(invoked1) shouldEventually] beYes];
);
);
context(@"singleInstance addObserver", ^
it(@"JKFactory", ^
JKFactory *factory = [JKFactory sharedInstance];
JKWorker *worker = [JKWorker new];
__block BOOL invoked1 = NO;
[factory jk_addObserver:worker forKeyPath:@"name" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
[[[change objectForKey:@"new"] should] equal:@"北京"];
invoked1 = YES;
];
factory.name = @"北京";
NSArray *array = [JKKVOItemManager items];
[[array should] haveCountOf:1];
[[theValue(invoked1) shouldEventually] beYes];
);
afterAll(^
NSArray *array = [JKKVOItemManager items];
[[array should] haveCountOf:1];
);
);
context(@"addObserver context", ^
beforeAll(^
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array)
[JKKVOItemManager removeItem:item];
);
afterEach(^
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array)
[JKKVOItemManager removeItem:item];
);
it(@"no context", ^
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
__block BOOL invoked1 = NO;
__block BOOL invoked2 = NO;
[worker jk_addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
[[[change objectForKey:@"new"] should] equal:@"zhangsan"];
invoked1 = YES;
];
[worker jk_addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
[[[change objectForKey:@"new"] should] equal:@"zhangsan"];
invoked2 = YES;
];
worker.name = @"zhangsan";
[[theValue(invoked1) shouldEventually] beYes];
[[theValue(invoked2) shouldEventually] beNo];
NSArray *array = [JKKVOItemManager items];
[[array should] haveCountOf:1];
);
it(@"has context", ^
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
__block BOOL invoked1 = NO;
__block BOOL invoked2 = NO;
void *aaa = &aaa;
[worker jk_addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:aaa withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
[[[change objectForKey:@"new"] should] equal:@"zhangsan"];
invoked1 = YES;
];
void *bbb = &bbb;
[worker jk_addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew
context:bbb withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
[[[change objectForKey:@"new"] should] equal:@"zhangsan"];
invoked2 = YES;
];
worker.name = @"zhangsan";
[[theValue(invoked1) shouldEventually] beYes];
[[theValue(invoked2) shouldEventually] beYes];
NSArray *array = [JKKVOItemManager items];
[[array should] haveCountOf:2];
);
);
context(@"object", ^
beforeAll(^
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array)
[JKKVOItemManager removeItem:item];
);
afterEach(^
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array)
[JKKVOItemManager removeItem:item];
);
it(@"jk_observeredKeyPaths", ^
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
[person jk_addObserver:worker forKeyPath:@"name" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
[person jk_addObserver:worker forKeyPath:@"age" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
NSArray *keyPaths = [person jk_observeredKeyPaths];
[[keyPaths should] haveCountOf:2];
);
it(@"jk_observersOfKeyPath:1", ^
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
[person jk_addObserver:worker forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
void *aaa = &aaa;
[person jk_addObserver:worker forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:aaa withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
NSArray *observers = [person jk_observersOfKeyPath:@"name"];
[[observers should] haveCountOf:1];
);
it(@"jk_observersOfKeyPath:2", ^
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
JKWorker *worker1 = [JKWorker new];
[person jk_addObserver:worker forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
[person jk_addObserver:worker1 forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
NSArray *observers = [person jk_observersOfKeyPath:@"name"];
[[observers should] haveCountOf:2];
);
it(@"jk_keyPathsObserveredBy:", ^
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
[person jk_addObserver:worker forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
[person jk_addObserver:worker forKeyPath:@"age" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
NSArray *keyPaths = [person jk_keyPathsObserveredBy:worker];
[[keyPaths should] haveCountOf:2];
);
);
context(@"remove", ^
beforeAll(^
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array)
[JKKVOItemManager removeItem:item];
);
afterEach(^
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array)
[JKKVOItemManager removeItem:item];
);
it(@"jk_removeObserver:forKeyPath:", ^
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
[worker jk_addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
[worker jk_removeObserver:person forKeyPath:@"name"];
NSArray *array = [JKKVOItemManager items];
[[theValue([array count]) should] equal:theValue(0)];
);
it(@"jk_removeObserver:forKeyPath:context:", ^
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
[worker jk_addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
void *aaa = &aaa;
[worker jk_addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:aaa withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
NSArray *array = [JKKVOItemManager items];
[[theValue([array count]) should] equal:theValue(2)];
[worker jk_removeObserver:person forKeyPath:@"name" context:aaa];
NSArray *array1 = [JKKVOItemManager items];
[[theValue([array1 count]) should] equal:theValue(1)];
JKKVOItem *item = array1.firstObject;
[[item.keyPath should] equal:@"name"];
);
it(@"jk_removeObserver:forKeyPaths:", ^
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
[person jk_addObserver:worker forKeyPath:@"name" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
[person jk_addObserver:worker forKeyPath:@"age" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
NSArray *keyPaths = [person jk_observeredKeyPaths];
[[keyPaths should] haveCountOf:2];
[person jk_removeObserver:worker forKeyPaths:keyPaths];
NSArray *array = [JKKVOItemManager items];
[[array should] haveCountOf:0];
);
it(@"jk_removeObservers:forKeyPath:", ^
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
JKWorker *worker1 = [JKWorker new];
[person jk_addObserver:worker forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
[person jk_addObserver:worker1 forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
];
NSArray *observers = [person jk_observersOfKeyPath:@"name"];
[[observers should] haveCountOf:2];
[person jk_removeObservers:observers forKeyPath:@"name"];
NSArray *array = [JKKVOItemManager items];
[[array should] haveCountOf:0];
);
it(@"for循环内快速创建对象", ^
NSMutableSet *set = [NSMutableSet new];
for (NSInteger i = 0; i < 20; i++)
JKPersonModel *person = [JKPersonModel new];
NSInteger itemCount = [JKKVOItemManager items].count;
__block BOOL invoked = NO;
[person jk_addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context)
invoked = YES;
];
person.name = [NSString stringWithFormat:@"name:%@",@(i)];
[[theValue(invoked) should] beYes];
[[theValue(itemCount + 1) should] equal:theValue([JKKVOItemManager items].count)];
NSString *address = [NSString stringWithFormat:@"%p",person];
[set addObject:address];
NSLog(@"amodel %@",address);
NSLog(@"count %@",@([set count]));
[[set shouldNot] haveCountOf:20];
[[[JKKVOItemManager items] should] haveCountOf:0];
);
);
context(@"array action", ^
afterEach(^
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array)
[JKKVOItemManager removeItem:item];
);
it(@"init", ^
JKTeacher *teacher = [JKTeacher new];
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[[change objectForKey:@"new"] should] haveCountOf:0];
invoked = YES;
];
teacher.students = @[].mutableCopy;
[[theValue(invoked) shouldEventually] beYes];
);
it(@"jk_addObject", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[[change objectForKey:@"new"] should] haveCountOf:1];
[[theValue(changedModel.changeType) should] equal:theValue(JKKVOArrayChangeTypeAddTail)];
[[changedModel.changedElements.firstObject.object should] equal:person1];
[[theValue(changedModel.changedElements.firstObject.newIndex) should] equal:theValue(0)];
[[theValue(changedModel.changedElements.firstObject.oldIndex) should] equal:theValue(NSNotFound)];
invoked = YES;
];
[students kvo_addObject:person1];
[[theValue(invoked) shouldEventually] beYes];
);
it(@"jk_insertObject:atIndex:", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[students kvo_addObject:person2];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[[change objectForKey:@"new"] should] haveCountOf:3];
[[theValue(changedModel.changeType) should] equal:theValue(JKKVOArrayChangeTypeAddAtIndex)];
[[changedModel.changedElements should] haveCountOf:1];
[[changedModel.changedElements.firstObject.object should] equal:person3];
[[theValue(changedModel.changedElements.firstObject.newIndex) should] equal:theValue(1)];
[[theValue(changedModel.changedElements.firstObject.oldIndex) should] equal:theValue(NSNotFound)];
invoked = YES;
];
[students kvo_insertObject:person3 atIndex:1];
[[theValue(invoked) shouldEventually] beYes];
);
it(@"jk_insertObject:atIndex:1", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[students kvo_addObject:person2];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[[change objectForKey:@"new"] should] haveCountOf:3];
[[theValue(changedModel.changeType) should] equal:theValue(JKKVOArrayChangeTypeAddAtIndex)];
[[changedModel.changedElements should] haveCountOf:1];
[[changedModel.changedElements.firstObject.object should] equal:person3];
[[theValue(changedModel.changedElements.firstObject.newIndex) should] equal:theValue(2)];
[[theValue(changedModel.changedElements.firstObject.oldIndex) should] equal:theValue(NSNotFound)];
invoked = YES;
];
[students kvo_insertObject:person3 atIndex:2];
[[theValue(invoked) shouldEventually] beYes];
);
it(@"jk_removeLastObject", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[students kvo_addObject:person2];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
[students kvo_addObject:person3];
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[[change objectForKey:@"new"] should] haveCountOf:2];
[[theValue(changedModel.changeType) should] equal:theValue(JKKVOArrayChangeTypeRemoveTail)];
[[changedModel.changedElements should] haveCountOf:1];
JKPersonModel *tmpPerson = (JKPersonModel *)changedModel.changedElements.firstObject.object;
NSLog(@"person %@",tmpPerson.name);
[[changedModel.changedElements.firstObject.object should] equal:person3];
[[theValue(changedModel.changedElements.firstObject.newIndex) should] equal:theValue(NSNotFound)];
[[theValue(changedModel.changedElements.firstObject.oldIndex) should] equal:theValue(2)];
invoked = YES;
];
[students kvo_removeLastObject];
[[theValue(invoked) shouldEventually] beYes];
);
it(@"jk_removeObjectAtIndex", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[students kvo_addObject:person2];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
[students kvo_addObject:person3];
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[[change objectForKey:@"new"] should] haveCountOf:2];
[[theValue(changedModel.changeType) should] equal:theValue(JKKVOArrayChangeTypeRemoveAtIndex)];
[[changedModel.changedElements should] haveCountOf:1];
[[changedModel.changedElements.firstObject.object should] equal:person2];
[[theValue(changedModel.changedElements.firstObject.newIndex) should] equal:theValue(NSNotFound)];
[[theValue(changedModel.changedElements.firstObject.oldIndex) should] equal:theValue(1)];
invoked = YES;
];
[students kvo_removeObjectAtIndex:1];
[[theValue(invoked) shouldEventually] beYes];
);
it(@"jk_replaceObjectAtIndex:withObject:", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[students kvo_addObject:person2];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
[students kvo_addObject:person3];
JKPersonModel *person4 = [JKPersonModel new];
person4.name = @"4";
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[[change objectForKey:@"new"] should] haveCountOf:3];
[[theValue(changedModel.changeType) should] equal:theValue(JKKVOArrayChangeTypeReplace)];
[[changedModel.changedElements should] haveCountOf:2];
[[changedModel.changedElements.lastObject.object should] equal:person4];
[[theValue(changedModel.changedElements.firstObject.newIndex) should] equal:theValue(NSNotFound)];
[[theValue(changedModel.changedElements.firstObject.oldIndex) should] equal:theValue(2)];
[[theValue(changedModel.changedElements.lastObject.newIndex) should] equal:theValue(2)];
[[theValue(changedModel.changedElements.lastObject.oldIndex) should] equal:theValue(NSNotFound)];
invoked = YES;
];
[students kvo_replaceObjectAtIndex:2 withObject:person4];
[[theValue(invoked) shouldEventually] beYes];
);
it(@"jk_addObjectsFromArray:", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[students kvo_addObject:person2];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
JKPersonModel *person4 = [JKPersonModel new];
NSArray *array = @[person3,person4];
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[[change objectForKey:@"new"] should] haveCountOf:4];
[[theValue(changedModel.changeType) should] equal:theValue(JKKVOArrayChangeTypeAddTail)];
[[changedModel.changedElements should] haveCountOf:2];
[[changedModel.changedElements.firstObject.object should] equal:person3];
[[theValue(changedModel.changedElements.firstObject.newIndex) should] equal:theValue(2)];
[[theValue(changedModel.changedElements.firstObject.oldIndex) should] equal:theValue(NSNotFound)];
[[changedModel.changedElements.lastObject.object should] equal:person4];
[[theValue(changedModel.changedElements.lastObject.newIndex) should] equal:theValue(3)];
[[theValue(changedModel.changedElements.lastObject.oldIndex) should] equal:theValue(NSNotFound)];
invoked = YES;
];
[students kvo_addObjectsFromArray:array];
[[theValue(invoked) shouldEventually] beYes];
);
it(@"jk_exchangeObjectAtIndex:withObjectAtIndex:", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[students kvo_addObject:person2];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
[students kvo_addObject:person3];
JKPersonModel *person4 = [JKPersonModel new];
person4.name = @"4";
[students kvo_addObject:person4];
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[[change objectForKey:@"new"] should] haveCountOf:4];
[[theValue(changedModel.changeType) should] equal:theValue(JKKVOArrayChangeTypeReplace)];
[[changedModel.changedElements should] haveCountOf:2];
[[changedModel.changedElements.firstObject.object should] equal:person3];
[[theValue(changedModel.changedElements.firstObject.newIndex) should] equal:theValue(3)];
[[theValue(changedModel.changedElements.firstObject.oldIndex) should] equal:theValue(2)];
[[changedModel.changedElements.lastObject.object should] equal:person4];
[[theValue(changedModel.changedElements.lastObject.newIndex) should] equal:theValue(2)];
[[theValue(changedModel.changedElements.lastObject.oldIndex) should] equal:theValue(3)];
invoked = YES;
];
[students kvo_exchangeObjectAtIndex:2 withObjectAtIndex:3];
[[theValue(invoked) shouldEventually] beYes];
);
it(@"jk_removeAllObjects", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[students kvo_addObject:person2];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
[students kvo_addObject:person3];
JKPersonModel *person4 = [JKPersonModel new];
person4.name = @"4";
[students kvo_addObject:person4];
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[[change objectForKey:@"new"] should] haveCountOf:0];
[[theValue(changedModel.changeType) should] equal:theValue(JKKVOArrayChangeTypeRemoveTail)];
[[changedModel.changedElements should] haveCountOf:4];
[[changedModel.changedElements[0].object should] equal:person1];
[[theValue(changedModel.changedElements[0].newIndex) should] equal:theValue(NSNotFound)];
[[theValue(changedModel.changedElements[0].oldIndex) should] equal:theValue(0)];
[[changedModel.changedElements[1].object should] equal:person2];
[[theValue(changedModel.changedElements[1].newIndex) should] equal:theValue(NSNotFound)];
[[theValue(changedModel.changedElements[1].oldIndex) should] equal:theValue(1)];
[[changedModel.changedElements[2].object should] equal:person3];
[[theValue(changedModel.changedElements[2].newIndex) should] equal:theValue(NSNotFound)];
[[theValue(changedModel.changedElements[2].oldIndex) should] equal:theValue(2)];
[[changedModel.changedElements[3].object should] equal:person4];
[[theValue(changedModel.changedElements[3].newIndex) should] equal:theValue(NSNotFound)];
[[theValue(changedModel.changedElements[3].oldIndex) should] equal:theValue(3)];
invoked = YES;
];
[students kvo_removeAllObjects];
[[theValue(invoked) shouldEventually] beYes];
);
it(@"jk_removeObject:", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
[students kvo_addObject:person1];
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary * change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[[change objectForKey:@"new"] should] haveCountOf:0];
[[theValue(changedModel.changeType) should] equal:theValue(JKKVOArrayChangeTypeRemoveAtIndex)];
[[changedModel.changedElements should] haveCountOf:2];
[[changedModel.changedElements.firstObject.object should] equal:person1];
[[theValue(changedModel.changedElements.firstObject.newIndex) should] equal:theValue(NSNotFound)];
[[theValue(changedModel.changedElements.firstObject.oldIndex) should] equal:theValue(0)];
[[changedModel.changedElements.lastObject.object should] equal:person1];
[[theValue(changedModel.changedElements.lastObject.newIndex) should] equal:theValue(NSNotFound)];
[[theValue(changedModel.changedElements.lastObject.oldIndex) should] equal:theValue(1)];
invoked = YES;
];
[students kvo_removeObject:person1];
[[theValue(invoked) shouldEventually] beYes];
);
it(@"element's property change", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil elementKeyPaths:@[@"name"] withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
[[theValue(changedModel.changeType) should] equal:theValue(JKKVOArrayChangeTypeElement)];
[[changedModel.changedElements should] haveCountOf:1];
[[changedModel.changedElements.firstObject.object should] equal:person1];
[[theValue(changedModel.changedElements.firstObject.newIndex) should] equal:theValue(0)];
[[theValue(changedModel.changedElements.firstObject.oldIndex) should] equal:theValue(0)];
invoked = YES;
];
person1.name = @"2";
[[theValue(invoked) shouldEventually] beYes];
);
it(@"jk_removeObservers", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil elementKeyPaths:@[@"name"] withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
invoked = YES;
];
person1.name = @"2";
[[theValue(invoked) shouldEventually] beYes];
[teacher jk_removeObservers];
);
it(@"jk_removeObserver:forKeyPath:context:", ^
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[students kvo_addObject:person1];
__block BOOL invoked = NO;
[teacher jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew context:nil elementKeyPaths:@[@"name"] withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context)
invoked = YES;
];
person1.name = @"2";
[[theValue(invoked) shouldEventually] beYes];
[teacher jk_removeObserver:teacher forKeyPath:@"students" context:nil];
);
);
context(@"observerd is nil", ^
beforeAll(^
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array)
[JKKVOItemManager removeItem:item];
);
it(@"observerd is nil", ^
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
[person jk_addObserver:worker forKeyPath:@"name" options:NSKeyValueObservingOptionNew withBlock:^(NSDictionary 以上是关于iOS基于KVO实现响应式编程之完结篇的主要内容,如果未能解决你的问题,请参考以下文章