mirror of
https://github.com/zhigang1992/ReactiveViewModel.git
synced 2026-01-12 22:51:31 +08:00
Merge pull request #1 from ReactiveCocoa/base-view-model
Add RVMViewModel base class
This commit is contained in:
@@ -27,6 +27,14 @@
|
||||
D0948B3C17815E7300BA8F23 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0948B3B17815E7300BA8F23 /* SenTestingKit.framework */; };
|
||||
D0948B3D17815E7800BA8F23 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0948B3B17815E7300BA8F23 /* SenTestingKit.framework */; };
|
||||
D0948B3F17815E9700BA8F23 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0948B3E17815E9700BA8F23 /* UIKit.framework */; };
|
||||
D0948B551781610600BA8F23 /* RVMViewModel.h in Headers */ = {isa = PBXBuildFile; fileRef = D0948B531781610600BA8F23 /* RVMViewModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D0948B561781610600BA8F23 /* RVMViewModel.h in Headers */ = {isa = PBXBuildFile; fileRef = D0948B531781610600BA8F23 /* RVMViewModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D0948B571781610600BA8F23 /* RVMViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = D0948B541781610600BA8F23 /* RVMViewModel.m */; };
|
||||
D0948B581781610600BA8F23 /* RVMViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = D0948B541781610600BA8F23 /* RVMViewModel.m */; };
|
||||
D0948B5A1781618A00BA8F23 /* RVMViewModelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0948B591781618A00BA8F23 /* RVMViewModelSpec.m */; };
|
||||
D0948B5B1781618A00BA8F23 /* RVMViewModelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0948B591781618A00BA8F23 /* RVMViewModelSpec.m */; };
|
||||
D0948B5E178161A800BA8F23 /* RVMTestViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = D0948B5D178161A800BA8F23 /* RVMTestViewModel.m */; };
|
||||
D0948B5F178161A800BA8F23 /* RVMTestViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = D0948B5D178161A800BA8F23 /* RVMTestViewModel.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -221,6 +229,11 @@
|
||||
D0948AE417815B4200BA8F23 /* Expecta.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Expecta.xcodeproj; path = External/ReactiveCocoa/external/expecta/Expecta.xcodeproj; sourceTree = "<group>"; };
|
||||
D0948B3B17815E7300BA8F23 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
D0948B3E17815E9700BA8F23 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
D0948B531781610600BA8F23 /* RVMViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RVMViewModel.h; sourceTree = "<group>"; };
|
||||
D0948B541781610600BA8F23 /* RVMViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RVMViewModel.m; sourceTree = "<group>"; };
|
||||
D0948B591781618A00BA8F23 /* RVMViewModelSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RVMViewModelSpec.m; sourceTree = "<group>"; };
|
||||
D0948B5C178161A800BA8F23 /* RVMTestViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RVMTestViewModel.h; sourceTree = "<group>"; };
|
||||
D0948B5D178161A800BA8F23 /* RVMTestViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RVMTestViewModel.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -311,6 +324,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0948A4B178159AD00BA8F23 /* ReactiveViewModel.h */,
|
||||
D0948B4A178160FC00BA8F23 /* View Model */,
|
||||
D0948A45178159AD00BA8F23 /* Supporting Files */,
|
||||
);
|
||||
path = ReactiveViewModel;
|
||||
@@ -341,6 +355,8 @@
|
||||
D0948A5C178159AD00BA8F23 /* ReactiveViewModelTests-Info.plist */,
|
||||
D0948A5D178159AD00BA8F23 /* InfoPlist.strings */,
|
||||
D0948ABC17815B1800BA8F23 /* ReactiveViewModelTests-Prefix.pch */,
|
||||
D0948B5C178161A800BA8F23 /* RVMTestViewModel.h */,
|
||||
D0948B5D178161A800BA8F23 /* RVMTestViewModel.m */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
@@ -412,6 +428,7 @@
|
||||
D0948ABD17815B2000BA8F23 /* Specs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0948B591781618A00BA8F23 /* RVMViewModelSpec.m */,
|
||||
);
|
||||
name = Specs;
|
||||
sourceTree = "<group>";
|
||||
@@ -451,6 +468,15 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0948B4A178160FC00BA8F23 /* View Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0948B531781610600BA8F23 /* RVMViewModel.h */,
|
||||
D0948B541781610600BA8F23 /* RVMViewModel.m */,
|
||||
);
|
||||
name = "View Model";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -459,6 +485,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0948ABA17815AF100BA8F23 /* ReactiveViewModel.h in Headers */,
|
||||
D0948B551781610600BA8F23 /* RVMViewModel.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -467,6 +494,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0948ABB17815AF100BA8F23 /* ReactiveViewModel.h in Headers */,
|
||||
D0948B561781610600BA8F23 /* RVMViewModel.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -756,6 +784,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0948B571781610600BA8F23 /* RVMViewModel.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -763,6 +792,8 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0948B5A1781618A00BA8F23 /* RVMViewModelSpec.m in Sources */,
|
||||
D0948B5E178161A800BA8F23 /* RVMTestViewModel.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -770,6 +801,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0948B581781610600BA8F23 /* RVMViewModel.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -777,6 +809,8 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0948B5B1781618A00BA8F23 /* RVMViewModelSpec.m in Sources */,
|
||||
D0948B5F178161A800BA8F23 /* RVMTestViewModel.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
74
ReactiveViewModel/RVMViewModel.h
Normal file
74
ReactiveViewModel/RVMViewModel.h
Normal file
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// RVMViewModel.h
|
||||
// ReactiveViewModel
|
||||
//
|
||||
// Created by Josh Abernathy on 9/11/12.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSignal;
|
||||
|
||||
// Adapts a domain model to be user-presentable, and implements behaviors that
|
||||
// drive the UI.
|
||||
@interface RVMViewModel : NSObject
|
||||
|
||||
// The model which the view model is adapting for the UI.
|
||||
@property (nonatomic, readonly, strong) id model;
|
||||
|
||||
// Whether the view model is currently "active."
|
||||
//
|
||||
// This generally implies that the associated view is visible. When set to NO,
|
||||
// the view model should throttle or cancel low-priority or UI-related work.
|
||||
//
|
||||
// This property defaults to NO.
|
||||
@property (nonatomic, assign, getter = isActive) BOOL active;
|
||||
|
||||
// Observes the receiver's `active` property, and sends the receiver whenever it
|
||||
// changes from NO to YES.
|
||||
//
|
||||
// If the receiver is currently active, this signal will send once immediately
|
||||
// upon subscription.
|
||||
@property (nonatomic, strong, readonly) RACSignal *didBecomeActiveSignal;
|
||||
|
||||
// Observes the receiver's `active` property, and sends the receiver whenever it
|
||||
// changes from YES to NO.
|
||||
//
|
||||
// If the receiver is currently inactive, this signal will send once immediately
|
||||
// upon subscription.
|
||||
@property (nonatomic, strong, readonly) RACSignal *didBecomeInactiveSignal;
|
||||
|
||||
// Calls -initWithModel: with a nil model.
|
||||
- (instancetype)init;
|
||||
|
||||
// Creates a new view model with the given model.
|
||||
//
|
||||
// model - The model to adapt for the UI. This argument may be nil.
|
||||
//
|
||||
// Returns an initialized view model, or nil if an error occurs.
|
||||
- (instancetype)initWithModel:(id)model;
|
||||
|
||||
// Subscribes (or resubscribes) to the given signal whenever
|
||||
// `didBecomeActiveSignal` fires.
|
||||
//
|
||||
// When `didBecomeInactiveSignal` fires, any active subscription to `signal` is
|
||||
// disposed.
|
||||
//
|
||||
// Returns a signal which forwards `next`s from the latest subscription to
|
||||
// `signal`, and completes when the receiver is deallocated. If `signal` sends
|
||||
// an error at any point, the returned signal will error out as well.
|
||||
- (RACSignal *)forwardSignalWhileActive:(RACSignal *)signal;
|
||||
|
||||
// Throttles events on the given signal while the receiver is inactive.
|
||||
//
|
||||
// Unlike -forwardSignalWhileActive:, this method will stay subscribed to
|
||||
// `signal` the entire time, except that its events will be throttled when the
|
||||
// receiver becomes inactive.
|
||||
//
|
||||
// Returns a signal which forwards events from `signal` (throttled while the
|
||||
// receiver is inactive), and completes when `signal` completes or the receiver
|
||||
// is deallocated.
|
||||
- (RACSignal *)throttleSignalWhileInactive:(RACSignal *)signal;
|
||||
|
||||
@end
|
||||
173
ReactiveViewModel/RVMViewModel.m
Normal file
173
ReactiveViewModel/RVMViewModel.m
Normal file
@@ -0,0 +1,173 @@
|
||||
//
|
||||
// RVMViewModel.m
|
||||
// ReactiveViewModel
|
||||
//
|
||||
// Created by Josh Abernathy on 9/11/12.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RVMViewModel.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <ReactiveCocoa/EXTScope.h>
|
||||
#import <ReactiveCocoa/ReactiveCocoa.h>
|
||||
|
||||
// The number of seconds by which signal events are throttled when using
|
||||
// -throttleSignalWhileInactive:.
|
||||
static const NSTimeInterval RVMViewModelInactiveThrottleInterval = 1;
|
||||
|
||||
@interface RVMViewModel ()
|
||||
|
||||
// Improves the performance of KVO on the receiver.
|
||||
//
|
||||
// See the documentation for <NSKeyValueObserving> for more information.
|
||||
@property (atomic) void *observationInfo;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RVMViewModel
|
||||
|
||||
#pragma mark Properties
|
||||
|
||||
// We create many, many view models, so these properties need to be as lazy and
|
||||
// memory-conscious as possible.
|
||||
@synthesize didBecomeActiveSignal = _didBecomeActiveSignal;
|
||||
@synthesize didBecomeInactiveSignal = _didBecomeInactiveSignal;
|
||||
|
||||
- (void)setActive:(BOOL)active {
|
||||
// Skip KVO notifications when the property hasn't actually changed. This is
|
||||
// especially important because self.active can have very expensive
|
||||
// observers attached.
|
||||
if (active == _active) return;
|
||||
|
||||
[self willChangeValueForKey:@keypath(self.active)];
|
||||
_active = active;
|
||||
[self didChangeValueForKey:@keypath(self.active)];
|
||||
}
|
||||
|
||||
- (RACSignal *)didBecomeActiveSignal {
|
||||
if (_didBecomeActiveSignal == nil) {
|
||||
@weakify(self);
|
||||
|
||||
_didBecomeActiveSignal = [[[RACObserve(self.active)
|
||||
filter:^(NSNumber *active) {
|
||||
return active.boolValue;
|
||||
}]
|
||||
map:^(id _) {
|
||||
@strongify(self);
|
||||
return self;
|
||||
}]
|
||||
setNameWithFormat:@"%@ -didBecomeActiveSignal", self];
|
||||
}
|
||||
|
||||
return _didBecomeActiveSignal;
|
||||
}
|
||||
|
||||
- (RACSignal *)didBecomeInactiveSignal {
|
||||
if (_didBecomeInactiveSignal == nil) {
|
||||
@weakify(self);
|
||||
|
||||
_didBecomeInactiveSignal = [[[RACObserve(self.active)
|
||||
filter:^ BOOL (NSNumber *active) {
|
||||
return !active.boolValue;
|
||||
}]
|
||||
map:^(id _) {
|
||||
@strongify(self);
|
||||
return self;
|
||||
}]
|
||||
setNameWithFormat:@"%@ -didBecomeInactiveSignal", self];
|
||||
}
|
||||
|
||||
return _didBecomeInactiveSignal;
|
||||
}
|
||||
|
||||
#pragma mark Lifecycle
|
||||
|
||||
- (id)init {
|
||||
return [self initWithModel:nil];
|
||||
}
|
||||
|
||||
- (id)initWithModel:(id)model {
|
||||
self = [super init];
|
||||
if (self == nil) return nil;
|
||||
|
||||
_model = model;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Activation
|
||||
|
||||
- (RACSignal *)forwardSignalWhileActive:(RACSignal *)signal {
|
||||
NSParameterAssert(signal != nil);
|
||||
|
||||
RACSignal *activeSignal = RACObserve(self.active);
|
||||
|
||||
return [[RACSignal
|
||||
createSignal:^(id<RACSubscriber> subscriber) {
|
||||
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
|
||||
|
||||
__block RACDisposable *signalDisposable = nil;
|
||||
|
||||
RACDisposable *activeDisposable = [activeSignal subscribeNext:^(NSNumber *active) {
|
||||
if (active.boolValue) {
|
||||
signalDisposable = [signal subscribeNext:^(id value) {
|
||||
[subscriber sendNext:value];
|
||||
} error:^(NSError *error) {
|
||||
[subscriber sendError:error];
|
||||
}];
|
||||
|
||||
if (signalDisposable != nil) [disposable addDisposable:signalDisposable];
|
||||
} else {
|
||||
[signalDisposable dispose];
|
||||
[disposable removeDisposable:signalDisposable];
|
||||
signalDisposable = nil;
|
||||
}
|
||||
} error:^(NSError *error) {
|
||||
[subscriber sendError:error];
|
||||
} completed:^{
|
||||
[subscriber sendCompleted];
|
||||
}];
|
||||
|
||||
if (activeDisposable != nil) [disposable addDisposable:activeDisposable];
|
||||
return disposable;
|
||||
}]
|
||||
setNameWithFormat:@"%@ -forwardSignalWhileActive: %@", self, signal];
|
||||
}
|
||||
|
||||
- (RACSignal *)throttleSignalWhileInactive:(RACSignal *)signal {
|
||||
NSParameterAssert(signal != nil);
|
||||
|
||||
signal = [signal replayLast];
|
||||
|
||||
return [[[[[RACObserve(self.active)
|
||||
takeUntil:[signal ignoreValues]]
|
||||
combineLatestWith:signal]
|
||||
throttle:RVMViewModelInactiveThrottleInterval valuesPassingTest:^ BOOL (RACTuple *xs) {
|
||||
BOOL active = [xs.first boolValue];
|
||||
return !active;
|
||||
}]
|
||||
reduceEach:^(NSNumber *active, id value) {
|
||||
return value;
|
||||
}]
|
||||
setNameWithFormat:@"%@ -throttleSignalWhileInactive: %@", self, signal];
|
||||
}
|
||||
|
||||
#pragma mark NSKeyValueObserving
|
||||
|
||||
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
|
||||
// We'll generate notifications for this property manually.
|
||||
if ([key isEqual:@keypath(RVMViewModel.new, active)]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [super automaticallyNotifiesObserversForKey:key];
|
||||
}
|
||||
|
||||
- (void)setNilValueForKey:(NSString *)key {
|
||||
// Ignore attempts to set primitive properties to nil. This is commonly
|
||||
// caused by RACObserve noticing an intermediate key change.
|
||||
//
|
||||
// See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/631.
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -6,3 +6,4 @@
|
||||
// Copyright (c) 2013 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RVMViewModel.h"
|
||||
|
||||
12
ReactiveViewModelTests/RVMTestViewModel.h
Normal file
12
ReactiveViewModelTests/RVMTestViewModel.h
Normal file
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// RVMTestViewModel.h
|
||||
// ReactiveViewModel
|
||||
//
|
||||
// Created by Josh Abernathy on 9/12/12.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RVMViewModel.h"
|
||||
|
||||
@interface RVMTestViewModel : RVMViewModel
|
||||
@end
|
||||
12
ReactiveViewModelTests/RVMTestViewModel.m
Normal file
12
ReactiveViewModelTests/RVMTestViewModel.m
Normal file
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// RVMTestViewModel.m
|
||||
// ReactiveViewModel
|
||||
//
|
||||
// Created by Josh Abernathy on 9/12/12.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RVMTestViewModel.h"
|
||||
|
||||
@implementation RVMTestViewModel
|
||||
@end
|
||||
192
ReactiveViewModelTests/RVMViewModelSpec.m
Normal file
192
ReactiveViewModelTests/RVMViewModelSpec.m
Normal file
@@ -0,0 +1,192 @@
|
||||
//
|
||||
// RVMViewModelSpec.m
|
||||
// ReactiveViewModel
|
||||
//
|
||||
// Created by Josh Abernathy on 9/11/12.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RVMTestViewModel.h"
|
||||
|
||||
SpecBegin(RVMViewModel)
|
||||
|
||||
__block RVMTestViewModel *viewModel;
|
||||
|
||||
beforeEach(^{
|
||||
viewModel = [[RVMTestViewModel alloc] initWithModel:@"foobar"];
|
||||
});
|
||||
|
||||
describe(@"active property", ^{
|
||||
it(@"should default to NO", ^{
|
||||
expect(viewModel.active).to.beFalsy();
|
||||
});
|
||||
|
||||
it(@"should send on didBecomeActiveSignal when set to YES", ^{
|
||||
__block NSUInteger nextEvents = 0;
|
||||
[viewModel.didBecomeActiveSignal subscribeNext:^(RVMViewModel *viewModel) {
|
||||
expect(viewModel).to.beIdenticalTo(viewModel);
|
||||
expect(viewModel.active).to.beTruthy();
|
||||
|
||||
nextEvents++;
|
||||
}];
|
||||
|
||||
expect(nextEvents).to.equal(0);
|
||||
|
||||
viewModel.active = YES;
|
||||
expect(nextEvents).to.equal(1);
|
||||
|
||||
// Indistinct changes should not trigger the signal again.
|
||||
viewModel.active = YES;
|
||||
expect(nextEvents).to.equal(1);
|
||||
|
||||
viewModel.active = NO;
|
||||
viewModel.active = YES;
|
||||
expect(nextEvents).to.equal(2);
|
||||
});
|
||||
|
||||
it(@"should send on didBecomeInactiveSignal when set to NO", ^{
|
||||
__block NSUInteger nextEvents = 0;
|
||||
[viewModel.didBecomeInactiveSignal subscribeNext:^(RVMViewModel *viewModel) {
|
||||
expect(viewModel).to.beIdenticalTo(viewModel);
|
||||
expect(viewModel.active).to.beFalsy();
|
||||
|
||||
nextEvents++;
|
||||
}];
|
||||
|
||||
expect(nextEvents).to.equal(1);
|
||||
|
||||
viewModel.active = YES;
|
||||
viewModel.active = NO;
|
||||
expect(nextEvents).to.equal(2);
|
||||
|
||||
// Indistinct changes should not trigger the signal again.
|
||||
viewModel.active = NO;
|
||||
expect(nextEvents).to.equal(2);
|
||||
});
|
||||
|
||||
describe(@"signal manipulation", ^{
|
||||
__block NSMutableArray *values;
|
||||
__block NSArray *expectedValues;
|
||||
__block BOOL completed;
|
||||
__block BOOL deallocated;
|
||||
|
||||
__block RVMTestViewModel * (^createViewModel)();
|
||||
|
||||
beforeEach(^{
|
||||
values = [NSMutableArray array];
|
||||
expectedValues = @[];
|
||||
completed = NO;
|
||||
deallocated = NO;
|
||||
|
||||
createViewModel = ^{
|
||||
RVMTestViewModel *viewModel = [[RVMTestViewModel alloc] initWithModel:nil];
|
||||
[viewModel.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
|
||||
deallocated = YES;
|
||||
}]];
|
||||
|
||||
viewModel.active = YES;
|
||||
return viewModel;
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(^{
|
||||
expect(deallocated).will.beTruthy();
|
||||
expect(completed).to.beTruthy();
|
||||
});
|
||||
|
||||
it(@"should forward a signal", ^{
|
||||
@autoreleasepool {
|
||||
RVMTestViewModel *viewModel __attribute__((objc_precise_lifetime)) = createViewModel();
|
||||
|
||||
RACSignal *input = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) {
|
||||
[subscriber sendNext:@1];
|
||||
[subscriber sendNext:@2];
|
||||
return nil;
|
||||
}];
|
||||
|
||||
[[viewModel
|
||||
forwardSignalWhileActive:input]
|
||||
subscribeNext:^(NSNumber *x) {
|
||||
[values addObject:x];
|
||||
} completed:^{
|
||||
completed = YES;
|
||||
}];
|
||||
|
||||
expectedValues = @[ @1, @2 ];
|
||||
expect(values).to.equal(expectedValues);
|
||||
expect(completed).to.beFalsy();
|
||||
|
||||
viewModel.active = NO;
|
||||
|
||||
expect(values).to.equal(expectedValues);
|
||||
expect(completed).to.beFalsy();
|
||||
|
||||
viewModel.active = YES;
|
||||
|
||||
expectedValues = @[ @1, @2, @1, @2 ];
|
||||
expect(values).to.equal(expectedValues);
|
||||
expect(completed).to.beFalsy();
|
||||
}
|
||||
});
|
||||
|
||||
it(@"should throttle a signal", ^{
|
||||
@autoreleasepool {
|
||||
RVMTestViewModel *viewModel __attribute__((objc_precise_lifetime)) = createViewModel();
|
||||
RACSubject *subject = [RACSubject subject];
|
||||
|
||||
[[viewModel
|
||||
throttleSignalWhileInactive:[subject startWith:@0]]
|
||||
subscribeNext:^(NSNumber *x) {
|
||||
[values addObject:x];
|
||||
} completed:^{
|
||||
completed = YES;
|
||||
}];
|
||||
|
||||
expectedValues = @[ @0 ];
|
||||
expect(values).to.equal(expectedValues);
|
||||
expect(completed).to.beFalsy();
|
||||
|
||||
[subject sendNext:@1];
|
||||
|
||||
expectedValues = @[ @0, @1 ];
|
||||
expect(values).to.equal(expectedValues);
|
||||
expect(completed).to.beFalsy();
|
||||
|
||||
viewModel.active = NO;
|
||||
|
||||
// Since the VM is inactive, these events should be throttled.
|
||||
[subject sendNext:@2];
|
||||
[subject sendNext:@3];
|
||||
|
||||
expect(values).to.equal(expectedValues);
|
||||
expect(completed).to.beFalsy();
|
||||
|
||||
expectedValues = @[ @0, @1, @3 ];
|
||||
expect(values).will.equal(expectedValues);
|
||||
expect(completed).to.beFalsy();
|
||||
|
||||
// After reactivating, we should still get this event.
|
||||
[subject sendNext:@4];
|
||||
viewModel.active = YES;
|
||||
|
||||
expectedValues = @[ @0, @1, @3, @4 ];
|
||||
expect(values).will.equal(expectedValues);
|
||||
expect(completed).to.beFalsy();
|
||||
|
||||
// And now new events should be instant.
|
||||
[subject sendNext:@5];
|
||||
|
||||
expectedValues = @[ @0, @1, @3, @4, @5 ];
|
||||
expect(values).to.equal(expectedValues);
|
||||
expect(completed).to.beFalsy();
|
||||
|
||||
[subject sendCompleted];
|
||||
|
||||
expect(values).to.equal(expectedValues);
|
||||
expect(completed).to.beTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
SpecEnd
|
||||
Reference in New Issue
Block a user