vlambda博客
学习文章列表

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
+ (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 (nonatomicweaknullablereadonly) __kindof NSArray *observered_property;

observered_property 是被监听对象的属性只能是数组类型,如果observered_property 不为空,是非数组类型的话会直接触发断言崩溃掉

@property (nonatomicstrongnullablereadonlyNSArray *elementKeyPaths;

这个是数组内的元素被监听的keyPath组成的数组,如果为nil那么数组内元素的变化不会触发回调,如果不为空的话,数组内元素会添加相应的监听。

/// 回调@property (nonatomiccopyreadonlyvoid(^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

遇到的坑点

内存陷阱问题

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]; } }}

待优化项

   由于这个框架底层使用了系统的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 * _Nonnull change, void * _Nonnull context) { }]; }); it(@"two object, one array jk_addObject", ^{ JKTeacher *teacher = [JKTeacher new]; NSMutableArray *students = [NSMutableArray new]; teacher.students = students; JKTeacher *teacher1 = [JKTeacher new]; teacher1.students = students; JKPersonModel *person1 = [JKPersonModel new]; person1.name = @"1"; __block BOOL invoked = NO; __block BOOL invoked1 = 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; }]; [teacher1 jk_addObserverOfArrayForKeyPath:@"students" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel, void * _Nonnull context) { [[[change objectForKey:NSKeyValueChangeNewKey] should] haveCountOf:1]; [[[change objectForKey:NSKeyValueChangeOldKey] should] haveCountOf:0];
[[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)]; invoked1 = YES; }]; [students kvo_addObject:person1]; [[theValue(invoked) shouldEventually] beYes]; [[theValue(invoked1) shouldEventually] beYes];
}); afterAll(^{ NSArray *array = [JKKVOItemManager items]; [[array should]haveCountOf:0]; });});
});
SPEC_END

pod 集成命令

pod 'JKKVOHelper'