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 * _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'

源码下载地址:https://github.com/xindizhiyin2014/JKKVOHelper.git
更多干货文章,欢迎扫描二维码关注公众号
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值