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 (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>
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进行变化的监听,对于一个数组中元素的属性发生变化的情况,如果数组的元素数量过多,那么会添加过多监听,性能变差。后续会考虑自定义实现底层的监听。
单元测试例子
为了充分自测自己的框架,这边我写了一些单元测试样例,供大家参考,示例如下:
SPEC_BEGIN(JKKVOHelperSpec)
describe(@"JKKVOHelper", ^{
context(@"addObserver", ^{
afterEach(^{
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array) {
[ ];
}
});
it(@"addObserver", ^{
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
__block BOOL invoked1 = NO;
[
[[[change objectForKey:@] should] equal:@"zhangsan"];
invoked1 = YES;
}];
worker.name = @"zhangsan";
NSArray *array = [JKKVOItemManager items];
[1]; ] haveCountOf:
[ ] beYes];
});
it(@"A observe B, B observe A", ^{
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
[
}];
[
}];
NSArray *array = [JKKVOItemManager items];
[2]; ] haveCountOf:
});
it(@"test observerKeyPaths", ^{
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
__block NSInteger invokedCout = 0;
[void * _Nonnull context) { ] options:NSKeyValueObservingOptionNew context:nil withDetailBlock:^(NSString * _Nonnull keyPath, NSDictionary * _Nonnull change,
if([keyPath isEqualToString:@"name"]){
[@"bbb"]; ] should] equal:
} else if ([keyPath isEqualToString:@"factory"]) {
JKFactory *factory = [change objectForKey:@"new"];
[@"China"]; ] equal:
}
invokedCout++;
}];
worker.name = @"bbb";
JKFactory *factory = [JKFactory new];
factory.name = @"China";
worker.factory = factory;
[2)]; ] equal:@(
});
it(@"test observer and observered are the same object", ^{
JKPersonModel *person = [JKPersonModel new];
__block BOOL invoked1 = NO;
[
[[[change objectForKey:@] should] equal:@"zhangsan"];
invoked1 = YES;
}];
person.name = @"zhangsan";
NSArray *array = [JKKVOItemManager items];
[1]; ] haveCountOf:
[ ] beYes];
});
});
context(@"singleInstance addObserver", ^{
it(@"JKFactory", ^{
JKFactory *factory = [JKFactory sharedInstance];
JKWorker *worker = [JKWorker new];
__block BOOL invoked1 = NO;
[
[[[change objectForKey:@] should] equal:@"北京"];
invoked1 = YES;
}];
factory.name = @"北京";
NSArray *array = [JKKVOItemManager items];
[1]; ] haveCountOf:
[ ] beYes];
});
afterAll(^{
NSArray *array = [JKKVOItemManager items];
[1]; ] haveCountOf:
});
});
context(@"addObserver context", ^{
beforeAll(^{
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array) {
[ ];
}
});
afterEach(^{
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array) {
[ ];
}
});
it(@"no context", ^{
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
__block BOOL invoked1 = NO;
__block BOOL invoked2 = NO;
[
[[[change objectForKey:@] should] equal:@"zhangsan"];
invoked1 = YES;
}];
[
[[[change objectForKey:@] should] equal:@"zhangsan"];
invoked2 = YES;
}];
worker.name = @"zhangsan";
[ ] beYes];
[ ] beNo];
NSArray *array = [JKKVOItemManager items];
[1]; ] haveCountOf:
});
it(@"has context", ^{
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
__block BOOL invoked1 = NO;
__block BOOL invoked2 = NO;
void *aaa = &aaa;
[
[[[change objectForKey:@] should] equal:@"zhangsan"];
invoked1 = YES;
}];
void *bbb = &bbb;
[
context:bbb withBlock:^(NSDictionary * _Nonnull change, void * _Nonnull context) {
[[[change objectForKey:@] should] equal:@"zhangsan"];
invoked2 = YES;
}];
worker.name = @"zhangsan";
[ ] beYes];
[ ] beYes];
NSArray *array = [JKKVOItemManager items];
[2]; ] haveCountOf:
});
});
context(@"object", ^{
beforeAll(^{
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array) {
[ ];
}
});
afterEach(^{
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array) {
[ ];
}
});
it(@"jk_observeredKeyPaths", ^{
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
[
}];
[
}];
NSArray *keyPaths = [person jk_observeredKeyPaths];
[2]; ] haveCountOf:
});
it(@"jk_observersOfKeyPath:1", ^{
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
[
}];
void *aaa = &aaa;
[
}];
NSArray *observers = [person jk_observersOfKeyPath:@"name"];
[1]; ] haveCountOf:
});
it(@"jk_observersOfKeyPath:2", ^{
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
JKWorker *worker1 = [JKWorker new];
[
}];
[
}];
NSArray *observers = [person jk_observersOfKeyPath:@"name"];
[2]; ] haveCountOf:
});
it(@"jk_keyPathsObserveredBy:", ^{
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
[
}];
[
}];
NSArray *keyPaths = [person jk_keyPathsObserveredBy:worker];
[2]; ] haveCountOf:
});
});
context(@"remove", ^{
beforeAll(^{
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array) {
[ ];
}
});
afterEach(^{
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array) {
[ ];
}
});
it(@"jk_removeObserver:forKeyPath:", ^{
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
[
}];
[ ];
NSArray *array = [JKKVOItemManager items];
[0)]; ]) should] equal:theValue(
});
it(@"jk_removeObserver:forKeyPath:context:", ^{
JKWorker *worker = [JKWorker new];
JKPersonModel *person = [JKPersonModel new];
[
}];
void *aaa = &aaa;
[
}];
NSArray *array = [JKKVOItemManager items];
[2)]; ]) should] equal:theValue(
[ ];
NSArray *array1 = [JKKVOItemManager items];
[1)]; ]) should] equal:theValue(
JKKVOItem *item = array1.firstObject;
[@"name"]; ] equal:
});
it(@"jk_removeObserver:forKeyPaths:", ^{
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
[
}];
[
}];
NSArray *keyPaths = [person jk_observeredKeyPaths];
[2]; ] haveCountOf:
[ ];
NSArray *array = [JKKVOItemManager items];
[0]; ] haveCountOf:
});
it(@"jk_removeObservers:forKeyPath:", ^{
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
JKWorker *worker1 = [JKWorker new];
[
}];
[
}];
NSArray *observers = [person jk_observersOfKeyPath:@"name"];
[2]; ] haveCountOf:
[ ];
NSArray *array = [JKKVOItemManager items];
[0]; ] haveCountOf:
});
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;
[
invoked = YES;
}];
person.name = [NSString stringWithFormat:@"name:%@",@(i)];
[ ] beYes];
[ ] equal:theValue([JKKVOItemManager items].count)];
NSString *address = [NSString stringWithFormat:@"%p",person];
[ ];
NSLog(@"amodel %@",address);
}
NSLog(@"count %@",@([set count]));
[20]; ] haveCountOf:
[0]; ] should] haveCountOf:
});
});
context(@"array action", ^{
afterEach(^{
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array) {
[ ];
}
});
it(@"init", ^{
JKTeacher *teacher = [JKTeacher new];
__block BOOL invoked = NO;
[
[[[change objectForKey:@] should] haveCountOf:0];
invoked = YES;
}];
teacher.students = @[].mutableCopy;
[ ] 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;
[
[[[change objectForKey:@] should] haveCountOf:1];
[ ] equal:theValue(JKKVOArrayChangeTypeAddTail)];
[ ] equal:person1];
[0)]; ] equal:theValue(
[ ] equal:theValue(NSNotFound)];
invoked = YES;
}];
[ ];
[ ] beYes];
});
it(@"jk_insertObject:atIndex:", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[ ];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
__block BOOL invoked = NO;
[
[[[change objectForKey:@] should] haveCountOf:3];
[ ] equal:theValue(JKKVOArrayChangeTypeAddAtIndex)];
[1]; ] haveCountOf:
[ ] equal:person3];
[1)]; ] equal:theValue(
[ ] equal:theValue(NSNotFound)];
invoked = YES;
}];
[ ];
[ ] beYes];
});
it(@"jk_insertObject:atIndex:1", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[ ];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
__block BOOL invoked = NO;
[
[[[change objectForKey:@] should] haveCountOf:3];
[ ] equal:theValue(JKKVOArrayChangeTypeAddAtIndex)];
[1]; ] haveCountOf:
[ ] equal:person3];
[2)]; ] equal:theValue(
[ ] equal:theValue(NSNotFound)];
invoked = YES;
}];
[ ];
[ ] beYes];
});
it(@"jk_removeLastObject", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[ ];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
[ ];
__block BOOL invoked = NO;
[
[[[change objectForKey:@] should] haveCountOf:2];
[ ] equal:theValue(JKKVOArrayChangeTypeRemoveTail)];
[1]; ] haveCountOf:
JKPersonModel *tmpPerson = (JKPersonModel *)changedModel.changedElements.firstObject.object;
NSLog(@"person %@",tmpPerson.name);
[ ] equal:person3];
[ ] equal:theValue(NSNotFound)];
[2)]; ] equal:theValue(
invoked = YES;
}];
[ ];
[ ] beYes];
});
it(@"jk_removeObjectAtIndex", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[ ];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
[ ];
__block BOOL invoked = NO;
[
[[[change objectForKey:@] should] haveCountOf:2];
[ ] equal:theValue(JKKVOArrayChangeTypeRemoveAtIndex)];
[1]; ] haveCountOf:
[ ] equal:person2];
[ ] equal:theValue(NSNotFound)];
[1)]; ] equal:theValue(
invoked = YES;
}];
[ ];
[ ] beYes];
});
it(@"jk_replaceObjectAtIndex:withObject:", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[ ];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
[ ];
JKPersonModel *person4 = [JKPersonModel new];
person4.name = @"4";
__block BOOL invoked = NO;
[
[[[change objectForKey:@] should] haveCountOf:3];
[ ] equal:theValue(JKKVOArrayChangeTypeReplace)];
[2]; ] haveCountOf:
[ ] equal:person4];
[ ] equal:theValue(NSNotFound)];
[2)]; ] equal:theValue(
[2)]; ] equal:theValue(
[ ] equal:theValue(NSNotFound)];
invoked = YES;
}];
[ ];
[ ] beYes];
});
it(@"jk_addObjectsFromArray:", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[ ];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
JKPersonModel *person4 = [JKPersonModel new];
NSArray *array = @[person3,person4];
__block BOOL invoked = NO;
[
[[[change objectForKey:@] should] haveCountOf:4];
[ ] equal:theValue(JKKVOArrayChangeTypeAddTail)];
[2]; ] haveCountOf:
[ ] equal:person3];
[2)]; ] equal:theValue(
[ ] equal:theValue(NSNotFound)];
[ ] equal:person4];
[3)]; ] equal:theValue(
[ ] equal:theValue(NSNotFound)];
invoked = YES;
}];
[ ];
[ ] beYes];
});
it(@"jk_exchangeObjectAtIndex:withObjectAtIndex:", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[ ];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
[ ];
JKPersonModel *person4 = [JKPersonModel new];
person4.name = @"4";
[ ];
__block BOOL invoked = NO;
[
[[[change objectForKey:@] should] haveCountOf:4];
[ ] equal:theValue(JKKVOArrayChangeTypeReplace)];
[2]; ] haveCountOf:
[ ] equal:person3];
[3)]; ] equal:theValue(
[2)]; ] equal:theValue(
[ ] equal:person4];
[2)]; ] equal:theValue(
[3)]; ] equal:theValue(
invoked = YES;
}];
[ ];
[ ] beYes];
});
it(@"jk_removeAllObjects", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
JKPersonModel *person2 = [JKPersonModel new];
person2.name = @"2";
[ ];
JKPersonModel *person3 = [JKPersonModel new];
person3.name = @"3";
[ ];
JKPersonModel *person4 = [JKPersonModel new];
person4.name = @"4";
[ ];
__block BOOL invoked = NO;
[
[[[change objectForKey:@] should] haveCountOf:0];
[ ] equal:theValue(JKKVOArrayChangeTypeRemoveTail)];
[4]; ] haveCountOf:
[object should] equal:person1]; ].
[ ].newIndex) should] equal:theValue(NSNotFound)];
[0)]; ].oldIndex) should] equal:theValue(
[object should] equal:person2]; ].
[ ].newIndex) should] equal:theValue(NSNotFound)];
[1)]; ].oldIndex) should] equal:theValue(
[object should] equal:person3]; ].
[ ].newIndex) should] equal:theValue(NSNotFound)];
[2)]; ].oldIndex) should] equal:theValue(
[object should] equal:person4]; ].
[ ].newIndex) should] equal:theValue(NSNotFound)];
[3)]; ].oldIndex) should] equal:theValue(
invoked = YES;
}];
[ ];
[ ] beYes];
});
it(@"jk_removeObject:", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
[ ];
__block BOOL invoked = NO;
[
[[[change objectForKey:@] should] haveCountOf:0];
[ ] equal:theValue(JKKVOArrayChangeTypeRemoveAtIndex)];
[2]; ] haveCountOf:
[ ] equal:person1];
[ ] equal:theValue(NSNotFound)];
[0)]; ] equal:theValue(
[ ] equal:person1];
[ ] equal:theValue(NSNotFound)];
[1)]; ] equal:theValue(
invoked = YES;
}];
[ ];
[ ] beYes];
});
it(@"element's property change", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
__block BOOL invoked = NO;
[void * _Nonnull context) { ] withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel,
[ ] equal:theValue(JKKVOArrayChangeTypeElement)];
[1]; ] haveCountOf:
[ ] equal:person1];
[0)]; ] equal:theValue(
[0)]; ] equal:theValue(
invoked = YES;
}];
person1.name = @"2";
[ ] beYes];
});
it(@"jk_removeObservers", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
__block BOOL invoked = NO;
[void * _Nonnull context) { ] withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel,
invoked = YES;
}];
person1.name = @"2";
[ ] beYes];
[ ];
});
it(@"jk_removeObserver:forKeyPath:context:", ^{
JKTeacher *teacher = [JKTeacher new];
NSMutableArray *students = [NSMutableArray new];
teacher.students = students;
JKPersonModel *person1 = [JKPersonModel new];
person1.name = @"1";
[ ];
__block BOOL invoked = NO;
[void * _Nonnull context) { ] withBlock:^(NSString * _Nonnull keyPath, NSDictionary *change, JKKVOArrayChangeModel * _Nonnull changedModel,
invoked = YES;
}];
person1.name = @"2";
[ ] beYes];
[ ];
});
});
context(@"observerd is nil", ^{
beforeAll(^{
NSArray *array = [JKKVOItemManager items];
for(JKKVOItem *item in array) {
[ ];
}
});
it(@"observerd is nil", ^{
JKPersonModel *person = [JKPersonModel new];
JKWorker *worker = [JKWorker new];
[
}];
});
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;
[
[[[change objectForKey:@] should] haveCountOf:1];
[ ] equal:theValue(JKKVOArrayChangeTypeAddTail)];
[ ] equal:person1];
[0)]; ] equal:theValue(
[ ] equal:theValue(NSNotFound)];
invoked = YES;
}];
[
[[[change objectForKey:NSKeyValueChangeNewKey] should] haveCountOf:1];
[0]; ] should] haveCountOf:
[ ] equal:theValue(JKKVOArrayChangeTypeAddTail)];
[ ] equal:person1];
[0)]; ] equal:theValue(
[ ] equal:theValue(NSNotFound)];
invoked1 = YES;
}];
[ ];
[ ] beYes];
[ ] beYes];
});
afterAll(^{
NSArray *array = [JKKVOItemManager items];
[0]; ]haveCountOf:
});
});
});
SPEC_END
pod 集成命令
pod 'JKKVOHelper'