The command is disabled and cannot be executed - objective-c

So, when I trying to fetch some data, RACCommand return this error.
I have a picker for example and when user scroll it, app get data from server and show them, but if user scroll fast, (previous operation in progress) RACCommand get this error:
Error Domain=RACCommandErrorDomain Code=1 "The command is disabled and cannot be executed" UserInfo={RACUnderlyingCommandErrorKey=<RACCommand: 0x174280050>, NSLocalizedDescription=The command is disabled and cannot be executed}
I know, its related with some cancel mechanism, but I tried many examples and not working as well.
Its my piece of code:
#weakify(self);
[[[self.viewModel makeCommand] execute:nil]
subscribeError:^(NSError *error) {
#strongify(self);
[self showAlertWithError:error];
}];
and viewModel:
- (RACCommand*)makeCommand {
if (!_makeCommand) {
_makeCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [self getVehicleMake];
}];
}
return _makeCommand;
}
- (RACSignal*)getVehicleMake {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[self.forumService getForumMakesWithYear:#([self.selectedYear integerValue])
category:self.vehicleCategory]
subscribeNext:^(RACTuple *result) {
self.makes = result.first;
[subscriber sendNext:self.makes];
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendCompleted];
}];
return [RACDisposable disposableWithBlock:^{
}];
}];
}

RACCommand doesn't allow concurrent execution by default. When it's executing, it becomes disabled. If you try to execute again, it will send that error.
But you can test for that error—RACCommand has RACCommandErrorDomain and RACCommandErrorNotEnabled constants available.
#weakify(self);
[[[self.viewModel makeCommand] execute:nil]
subscribeError:^(NSError *error) {
#strongify(self);
if ([error.domain isEqual:RACCommandErrorDomain] && error.code == RACCommandErrorNotEnabled) {
return;
}
[self showAlertWithError:error];
}];

Related

ReactiveCocoa chaining network operations failed

I tried to use ReactiveCocoa to do network operation chaining, but I failed. I can't figure out what is wrong with my code.
- (RACSignal *)pg_findObjectsInBackground {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[self findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
[subscriber sendError:error];
return;
}
[subscriber sendNext:objects];
[subscriber sendCompleted];
}];
return [RACDisposable disposableWithBlock:^{
[self cancel];
}];
}];
}
- (RACSignal *)pg_countObjectsInBackground {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[self countObjectsInBackgroundWithBlock:^(int number, NSError *error) {
if (error) {
[subscriber sendError:error];
return;
}
[subscriber sendNext:#(number)];
[subscriber sendCompleted];
}];
return [RACDisposable disposableWithBlock:^{
[self cancel];
}];
}];
}
__block NSError *_error;
#weakify(self)
[[[self.query pg_countObjectsInBackground]flattenMap:^RACStream *(NSNumber *count) {
#strongify(self)
self.totalCount = [count integerValue];
// Second, fetch experiences
self.query.limit = self.pageSize;
self.query.skip = self.pageSize * self.currentPage;
return [self.query pg_findObjectsInBackground];
}]subscribeNext:^(NSArray *experiences) {
#strongify(self)
[self.experiences removeAllObjects];
[self.experiences addObjectsFromArray:experiences];
} error:^(NSError *error) {
_error = error;
} completed:^{
#strongify(self)
if (finishBlock) {
finishBlock(self, _error);
}
}];
The first request was successful. But as soon as I return [self.query pg_findObjectsInBackground], it went to disposableWithBlock directly.
Because you're using the same PFQuery object for both the count and the find object operation, the query gets canceled when you return from the flattenMap method. The flattenMap subscribes to the new signal (which is the same signal), which I believe causes the disposable to fire. A simple solution is to construct a new PFQuery and return it in the flattenMap block.
I assumed you're using Parse, and if you are, you should tag it.

ReactiveCocoa signal for fetching data upon observation of authentication status

Fairly new to ReactiveCocoa, I'm trying to build a signal that asynchronously fetches some resource from a remote API to which the client has to authenticate first. Authentication is handled by first getting a token from the API, and then passing it via some custom HTTP header for each subsequent request. However, the custom header might be set after the fetchResource signal is subscribed to, which in the current situation leads to an unauthenticated request. I guess I could actually build the request in the subscribeNext block of self.authenticationStatus, thus ensuring that the token will be set, but how could I handle the disposition of the signal then?
- (RACSignal *)fetchResource
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSURLRequest *request = [self.requestSerializer
requestWithMethod:#"GET"
URLString:[[NSURL URLWithString:#"resource" relativeToURL:self.baseURL] absoluteString]
parameters:nil error:nil];
NSURLSessionDataTask *task = [self dataTaskWithRequest:request
completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
[subscriber sendError:error];
} else {
[subscriber sendNext:responseObject];
[subscriber sendCompleted];
}
}];
// Actually trigger the request only once the authentication token has been fetched.
[[self.authenticationStatus ignore:#NO] subscribeNext:^(id _) {
[task resume];
}];
return [RACDisposable disposableWithBlock:^{
[task cancel];
}];
}];
}
- (RACSignal *)fetchTokenWithCredentials:(Credentials *)credentials
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// Fetch the token and send it to `subscriber`.
Token *t = ... ;
[subscriber sendNext:t];
return nil;
}];
}
- (RACSignal *)fetchResourceWithToken:(Token *)token
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// Use `token` to set the request header. Then fetch
// the resource and send it to `subscriber`. Basically
// this part is what you already have.
Resource *r = ... ;
[subscriber sendNext:r];
return nil;
}];
}
In your view controller, present the modal authentication dialog if you don't have a valid token. When the user taps the "submit" button, do something like the following:
- (IBAction)handleAuthenticationSubmit:(id)sender
{
Credentials *c = ... ;
RACSignal *resourceSignal = [[[self fetchTokenWithCredentials:c]
flattenMap:^(Token *t) {
return [self fetchResourceWithToken:t];
}]
deliverOn:RACScheduler.mainThreadScheduler];
[self rac_liftSelector:#selector(receiveResource:) withSignals:resourceSignal, nil];
}
- (void)receiveResource:(Resource *)resource
{
[self.delegate authenticationController:self didReceiveResource:resource];
}

multiple testPerformance with XCTestCase objective-C

I have 2 performance test methods in test class. If i run them separately they pass. If i run hole class methods they fail with message:
**** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'API violation - multiple calls made to -[XCTestExpectation fulfill].'*
Is there any way to include couple performance tests in 1 class?
here is sample code:
- (void)testPerformanceAuthenticateWithLogin {
XCTestExpectation *authenticateExpectation = [self expectationWithDescription:#"Authenticate With Login"];
__block int userID = 0;
[self measureBlock:^{
[AuthenticationService authenticateWithLogin:email password:password success:^(AuthenticationResponse *result) {
XCTAssert(result.isAuthenticated);
userID = result.userID;
[authenticateExpectation fulfill];
} failure:^(NSError *error) {
XCTAssertNil(error);
}];
}];
[self waitForExpectationsWithTimeout:3 handler:^(NSError *error) {
XCTAssertNil(error);
[AuthenticationService logoutWithServicePersonID:userID success:nil failure:nil];
}];
}
- (void)testPerformanceGetServicePersonByID {
XCTestExpectation *getServicePersonExpectation = [self expectationWithDescription:#"get Service Person By ID"];
__block int userID = 0;
[AuthenticationService authenticateWithLogin:email password:password success:^(AuthenticationResponse *result) {
userID = result.userID;
[self loginSuccess:result];
[self measureBlock:^{
[ServicePersonService getServicePersonByIDWithServicePersonID:userID success:^(ServicePersonDTO *result) {
XCTAssertNotNil(result);
[getServicePersonExpectation fulfill];
} failure:^(NSError *error) {
XCTAssertNil(error);
}];
}];
} failure:^(NSError *error) {
XCTAssertNil(error);
}];
[self waitForExpectationsWithTimeout:3 handler:^(NSError *error) {
XCTAssertNil(error);
[AuthenticationService logoutWithServicePersonID:userID success:nil failure:nil];
}];
}
The measureBlock actually runs the code inside the block multiple times to determine an average value.
If I understand correctly you want to measure how long does the authentication take. If that's the case:
you could put the expectation inside the measure block so every time a measure iteration is done, the test iteration will wait for the expectation to be fulfilled before executing the block again
you could bump up the expectation timeout a bit just in case.
I've done something like this (which worked for me):
- (void)testPerformanceAuthenticateWithLogin {
[self measureBlock:^{
__block int userID = 0;
XCTestExpectation *authenticateExpectation = [self expectationWithDescription:#"Authenticate With Login"];
[AuthenticationService authenticateWithLogin:email password:password success:^(AuthenticationResponse *result) {
XCTAssert(result.isAuthenticated);
userID = result.userID;
[authenticateExpectation fulfill];
} failure:^(NSError *error) {
XCTAssertNil(error);
}];
[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
XCTAssertNil(error);
[AuthenticationService logoutWithServicePersonID:userID success:nil failure:nil];
}];
}];
}

How to use RACMulticastConnection for app components notification

Short version:
Is it possible to use RACMulticastConnection in the same way as NSNotificationCenter? I mean to keep the subscribed blocks valid even for another call [connection connect]?
Long version:
Among different subscribers I share a reference to RACMulticastConnection. Those who initiate the request pass loginRequest!=nil and those subscribers who want just listen use loginRequest==nil:
RACMulticastConnection *connection = [self.appModel loginRequest:loginRequest];
[connection connect]; //I initiate the request
[connection.signal subscribeNext:^(RACTuple* responseTuple) {
} error:^(NSError *error) {
} completed:^{
}];
In other modul I just subscribe and listen:
RACMulticastConnection *connection = [self.appModel loginRequest:nil];
[connection.signal subscribeNext:^(RACTuple* responseTuple) {
} error:^(NSError *error) {
} completed:^{
}];
When I call the [connection connect]; everything works fine. The subscribers are notified. But if I want to repeat the request to the server again with [connection connect]; I just receive successful signal with the old responses.
Basic idea is I want to create RACMulticastConnection once and share it for potential subscribers. Those who listen pass nil arguments, those who initiate the request pass not nil argument and call [connection connect]; But it does not trigger the block defined in RACSignal createSignal:.
The RACSignal is created just once when RACMulticastConnection does not exist. self.loginRequestConnection is property of model. The property is shared in the application to subscribers:
- (RACMulticastConnection*) loginRequest:(LoginRequest*)request {
self.loginRequest = request;
if(! self.loginRequestConnection) { // the instance of RACMulticastConnection shared among the subscribers
#weakify(self);
RACSignal* networkRequest = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
__block Response* blockResponse = nil;
#strongify(self);
[_serviceClient login:self.loginRequest success:^(TSNApiResponse *response, TSNError *error) {
blockResponse = response;
[subscriber sendNext:RACTuplePack(response, error)];
[subscriber sendCompleted];
} failure:^(TSNError *error) {
[self cleanUpRequestOnError:error subscriber:subscriber blockResponse:blockResponse blockRequest:self.loginRequest];
[subscriber sendError:error];
}];
return [RACDisposable disposableWithBlock:^{
[_serviceClient cancelRequest:self.loginRequest];
}];
}];
self.loginRequestConnection = [networkRequest multicast:[RACReplaySubject subject]];
}
return self.loginRequestConnection;
}
Is there any correct way how to make the connection trigger again the block in the RACSignal? Thank you.
I have used FBKVOController. The code done with FBKVOController is fraction of the implementation done with ReactiveCocoa:
- (void) loginRequest:(TSNLoginRequest*)request {
[_serviceClient login:request success:^(TSNApiResponse *response, TSNError *error) {
self.loginResponseTuple = [TSNResponseTuple responseTuple:response error:error];
} failure:^(TSNError *error) {
self.loginResponseTuple = [TSNResponseTuple responseTuple:nil error:error];
}];
}
Issuing the request:
[self.appModel loginRequest:loginRequest];
Observing the response:
[_KVOController observe:self.appModel keyPath:#"loginResponseTuple" options:NSKeyValueObservingOptionNew block:^(TSNStartupScreenViewModel* observer, TSNAppModel* observed, NSDictionary *change) {
TSNResponseTuple* responseTuple = change[NSKeyValueChangeNewKey];
#strongify(self);
if([responseTuple.error isError]) {
[TSNAppUtilities showError:(TSNError *)(responseTuple.error) completion:^(OHAlertView *alert, NSInteger buttonIndex) {
if(buttonIndex == alert.firstOtherButtonIndex) {
// ... process selection
}
}];
}
}];

Cancel RACCommand execution

Is there any way to cancel execution of a RACCommand?
For example I have a command with infinite execution signal like this:
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
__block BOOL stop = NO;
while (!stop) {
[subscriber sendNext:nil];
}
return [RACDisposable disposableWithBlock:^{
stop = YES;
}];
}];
}];
So how can I stop it after calling [command execute:nil]?
I'm a bit new to RACCommand, so I'm not certain there's a better way to do this. But I have been using takeUntil: with a cancellation signal to halt execution.
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
while (true) {
[subscriber sendNext:nil];
}
}] takeUntil:cancellationSignal];
}];