I have a semi-working approach to this problem. I have a search field that sets off a series of dependent cold RACSignals - in my case network requests (similar to Chaining dependent signals in ReactiveCocoa). If at any point during those network requests, the text changes again, I would like the current chain of signals to halt, and restart with the new search text. I am aware of functions like switchToLatest, and this thread: https://github.com/ReactiveCocoa/ReactiveCocoa/issues/468, but not how they apply to my situation. Here is an example in code of what I am trying to achieve:
RACSignal * (^longNetworkSignalWithValue)(NSString *) = ^(NSString * value){
return [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) {
usleep(2000000);
[subscriber sendNext:value];
}];
};
[[self.searchField.rac_textSignal throttle:.2] subscribeNext:^(id value) {
__block BOOL cancelled;
[[[self.searchField.rac_textSignal skip:1] take:1] subscribeNext:^(id x) {
NSLog(#"CANCELLED FOR : %#", value);
cancelled = YES;
}];
[[[longNetworkSignalWithValue(value) flattenMap:^RACStream *(id value) {
if(cancelled) return nil;
NSLog(#"Network signal 1 completed for: %#", value);
return longNetworkSignalWithValue(value);
}] flattenMap:^RACStream *(id value) {
if(cancelled) return nil;
NSLog(#"Network signal 2 completed for: %#", value);
return longNetworkSignalWithValue(value);
}] subscribeNext:^(id x) {
if(!cancelled)
NSLog(#"Completed with value: %#", x);
}];
}];
Is my cancelled BOOL the best way to accomplish what I am trying to do?
Related
I want to process data stream with ReactiveCocoa, what I want to do is, I want to calculate average value of the data stream for a period, then use the value to subtract with the average value. The flow char would look like this
Data source -> extract value ----> average---> zip
\ /
\------------/
So you can the extract value signal generates average signal, it also sends to zip to combine with average signal's result.
The code to demonstrate is here
__block id<RACSubscriber> sourceSubscriber;
RACSignal *sourceSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
sourceSubscriber = subscriber;
return nil;
}];
RACSignal *extractValueSignal = [sourceSignal map:^id(id value) {
NSLog(#"extractValueSignal %#", value);
return value;
}];
RACSignal *avgSignal = [extractValueSignal scanWithStart:[NSMutableArray array] reduce:^NSMutableArray *(NSMutableArray *array, id next) {
NSLog(#"avgSignal %#", next);
[array addObject:next];
if (array.count > 5) {
[array removeObjectsInRange:NSMakeRange(0, array.count - 5)];
}
return array;
}];
[[RACSignal zip:#[extractValueSignal, avgSignal] reduce:^id(NSNumber *value, NSNumber *avg) {
NSLog(#"zip %#, %#", value, avg);
return value;
}] subscribeNext:^(id x) {
NSLog(#"output %#", x);
}];
[sourceSubscriber sendNext:#1];
[sourceSubscriber sendNext:#2];
[sourceSubscriber sendNext:#3];
[sourceSubscriber sendNext:#4];
[sourceSubscriber sendNext:#5];
The output is
extractValueSignal 1
avgSignal 1
extractValueSignal 2
avgSignal 2
extractValueSignal 3
avgSignal 3
extractValueSignal 4
avgSignal 4
extractValueSignal 5
avgSignal 5
The problem I have here is the zip block never get called, nor the subscribeNext block. I wonder why it doesn't work? Shouldn't I to have one signal in the flow for twice or what? Should I use something like tee or what for extract value signal?
Okay, answering my own question, I noticed that, if you add a NSLog in createSignal block
RACSignal *sourceSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(#"SUB %#", subscriber);
sourceSubscriber = subscriber;
return nil;
}];
You will notice that it get called twice, as down stream subscribes to it twice, so the subscriber was replaced, so only one side of the signal get sent with next event.
To solve that problem, it's fairly easy, just create a multicast connection
RACMulticastConnection *conn = extractValueSignal.publish;
[conn connect];
then you can subscribe to
conn.signal
as much as you like, it will only call the block once.
I started writing a simple JSON RPC TCP library in Objective C.
I have a method that invokes a RPC Method:
- (void)invokeMethod:(NSString *)method
withParameters:(id)parameters
requestId:(id)requestId
success:(void (^)(id responseObject))success
failure:(void (^)(NSError *error))failure
{
NSAssert(NSClassFromString(#"NSJSONSerialization"), #"NSJSONSerialization not found!");
NSDictionary *requestObject = #{#"jsonrpc": #"2.0",
#"method": method,
#"params": parameters,
#"id": requestId};
NSError *error = nil;
NSData *jsondData = [NSJSONSerialization dataWithJSONObject:requestObject options:0 error:&error];
if (error){
return failure(error);
}
[self->callbacks setObject:#{#"success": success ? [success copy] : [NSNull null],
#"failure": failure ? [failure copy] : [NSNull null]}
forKey:requestId];
NSString *str = [[NSString alloc] initWithData:jsondData encoding:NSUTF8StringEncoding];
NSLog(#"Sending: %#", str);
[self.socket writeData:jsondData withTimeout:-1 tag:1];
}
The class basically represents a TCP connection, when calling the above method, the JSON data is sent with an id over TCP to the server which either returns a success or a failure:
- (void) socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
NSError *error = nil;
[self.socket readDataWithTimeout:-1 tag:2];
// … rpc response parsing code here, removed for simplicity …
// detect if error or success
NSDictionary *cbs = [self->callbacks objectForKey:JSONRPCObjectId];
void(^success)(id resultObject) = [cbs objectForKey:#"success"];
success ? success(JSONRPCObjectResult) : nil;
return;
}
Now, I am unsure how to keep track of the success and failure blocks, currently I am storing them in an NSMutableDict, using the requestId as key. Is it fine to do this or is there a better approach that I should use?
Blocks in objective-c are objects and you can treat the same way as other object, so storing them in NSDictionarys, NSArrays etc is perfectly fine. The only catch is that blocks when initially created exist in the same memory scope as local variable do and so they are no longer valid when the method that the block is defined in returns, just like all other local variables so you have to copy them first, just copy them and put the copy in the collection. There is a block copy function but you can just send them a copy message [myBlock copy];
Quick answer, seeing as you don't have anything workable yet...
This is more than you asked for; so, you'll probably have to pair it down to meet your specific need. Basically, it stores as many blocks as you specify at contiguous memory addresses. Paste this into a header file or somewhere global to the method from which you will call these:
typedef const typeof(id(^)(void)) retained_object;
static id (^retainable_object)(id(^)(void)) = ^ id (id(^object)(void)) {
return ^{
return object();
};
};
typeof (retained_object) *(^(^retain_object)(id (^__strong)(void)))(void) = ^ (id(^retainable_object)(void)) {
typeof(retained_object) * object_address;
object_address = &retainable_object;
typeof(retained_object) * persistent_object = (typeof(retained_object) *)CFBridgingRetain(retainable_object);
return ^ typeof(retained_object) * {
return persistent_object;
};
};
static void (^(^iterator)(const unsigned long))(id(^)(void)) = ^ (const unsigned long object_count) {
id const * retained_objects_ref[object_count];
return ^ (id const * retained_objects_t[]) {
return ^ (id(^object)(void)) {
object();
int index = 0UL;
int * index_t = &index;
for (; (*index_t) < object_count; ((*index_t) = (*index_t) + 1UL)) printf("retained_object: %p\n", (*((id * const)retained_objects_t + (object_count - index)) = retain_object(retainable_object(object()))));
};
}(retained_objects_ref);
};
From some method, add:
iterator(1000)(^ id { return (^{ printf("stored block\n"); }); });
This should store 1,000 blocks at as many unique memory addresses.
I'm still new to ReactiveCocoa. I wanted to simply add an observer to a field, so did it like this:
[_countryPicker rac_observeKeyPath:#"value" options:nil observer:self block:^(VBCountry* value, NSDictionary* change)
{
if ([_mobileField.textField.text length] == 0)
{
[_mobileField.textField setText:[NSString stringWithFormat:#"+%i", value.dialCode]];
}
}];
With block callback, and no need to explicitly detach the observer, this is already better than old-style KVO.
However, is this a low-level method to which there's a higher level of abstraction? If so, is it OK to call this method directly? And what's the better/higher way to do it?
I'd advise against depending on the direct KVO methods. They're really an implementation detail.
Let's progressively re-write that with idiomatic RAC operators.
First we'll just replace the direct KVO code with RACObserve:
[RACObserve(_countryPicker, value) subscribeNext:^(VBCountry *value) {
if ([_mobileField.textField.text length] == 0)
{
[_mobileField.textField setText:[NSString stringWithFormat:#"+%i", value.dialCode]];
}
}];
Then we'll replace the if and string formatting with -filter: and -map::
[[[RACObserve(_countryPicker, value) filter:^(id _) {
return [_mobileField.textField.text length] > 0;
}] map:^(VBCountry *value) {
return [NSString stringWithFormat:#"+%i", value.dialCode];
}] subscribeNext:^(NSString *text) {
[_mobileField.textField setText:text];
}];
Finally we'll use the RAC macro to make the assignment over time explicit:
RAC(_mobileField.textField, text) = [[RACObserve(_countryPicker, value) filter:^(id _) {
return [_mobileField.textField.text length] > 0;
}] map:^(VBCountry *value) {
return [NSString stringWithFormat:#"+%i", value.dialCode];
}];
Perhaps I'm still struggling on the reactive learning curve but I am having a hard time figuring out how to bridge a non reactive class with the rest of my reactive code. I am using a category to extend the non-reactive class.
The property is just an Enum representing the current state of a network action, states like New, Submitted, Processing and Completed. Right now I have written the following method in my category:
#implementation JRequestBase (RACExtensions)
- (RACSignal*) rac_RequestStateSignal
{
return RACAble(self, state);
}
#end
However, when state transitions from Processing -> Completed or from any state to Errored I want this signal to send Completed or Error instead of Next Value. How can I accomplish this in a category? I want to do something like:
#implementation JRequestBase (RACExtensions)
- (RACSignal*) rac_RequestStateSignal
{
return [RACAble(self, state) map:^(NSNumber *state){
if ([state intValue] == iRequestStateComplete)
{
# SEND COMPLETE
}
else if ([state intValue] == iRequestStateErrored)
{
# SEND ERROR
}
else
{
return state;
}
}];
}
#end
edit: I took a look at the GHAPIDemo and have come up with the following:
- (RACSignal*) rac_RequestSignal
{
RACSubject *subject = [[RACReplaySubject alloc] init];
[[RACAble(self, state) subscribeNext:^(NSNumber* s){
if ( [s intValue] == JRequestStateCompleted)
{
[subject sendNext:self];
[subject sendCompleted];
}
else if ([s intValue] == JRequestStateErrored)
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
// .. Set up dict with necessary values.
NSError *error = [NSError errorWithDomain:#"blah" code:1 userInfo:dict];
[subject sendError:error];
}
}];
return subject;
}
I'm not 100% sure this is the right way but it seems to be working.
Whenever you want to map values → signal events, instead of values → values, you should use -flattenMap: to return a signal corresponding to each input value. Then, as the "flatten" in the name implies, they'll be combined into one resulting signal.
However, this case is a little different, because you want to terminate the signal as soon as you get the Complete value. We'll use -takeUntilBlock: to represent that part.
The resulting code looks something like this:
- (RACSignal*) rac_RequestStateSignal
{
return [[RACObserve(self, state)
takeUntilBlock:^ BOOL (NSNumber *state){
return [state intValue] == iRequestStateComplete;
}]
flattenMap:^(NSNumber *state){
if ([state intValue] == iRequestStateErrored)
{
// Create a meaningful NSError here if you can.
return [RACSignal error:nil];
}
else
{
return [RACSignal return:state];
}
}];
}
(I used RACObserve because ReactiveCocoa 2.0 is now the only supported version, but you can use RACAble until you're ready to upgrade.)
As a general rule, you should avoid using subjects when possible, since they make code more stateful and reduce laziness.
I have these declarations in header file:
Note: I won't explain the whole code, I think it is easy to understand
typedef void (^loopCell)(id cell);
-(id)allCells:(loopCell)cell;
And allCells function implementation:
-(id)allCells:(loopCell)cell
{
for (AAFormSection *section in listSections)
{
for (id _cell in section.fields) {
cell(_cell);
}
}
return nil;
}
The usage of allCells function:
-(void)setFieldValue:(NSString *)value withID:(int)rowID
{
[self allCells:^(id cell) {
if([cell isKindOfClass:[AAFormField class]]) {
AAFormField *_cell = (AAFormField *)cell;
if(_cell.rowID == rowID) {
_cell.value = value;
//return; Here I want to terminate loop
}
}
}];
}
My problem is, I can't terminate allCells loop in the middle (actually when I found object I need in the loop, I don't want iterate through other objects)
How can I stop allCells loop in the middle?
Look at the docs for NSArray enumerateObjectsUsingBlock:. They setup the block signature to take a BOOL pointer. Set the stop BOOL to YES to cause the iteration to stop.
typedef void (^loopCell)(id cell, BOOL *stop);
-(id)allCells:(loopCell)cell {
BOOL stop = NO;
for (AAFormSection *section in listSections) {
for (id _cell in section.fields) {
cell(_cell, &stop);
if (stop) {
break;
}
}
if (stop) {
break;
}
}
return nil;
}
-(void)setFieldValue:(NSString *)value withID:(int)rowID {
[self allCells:^(id cell, BOOL *stop) {
if([cell isKindOfClass:[AAFormField class]]) {
AAFormField *_cell = (AAFormField *)cell;
if(_cell.rowID == rowID) {
_cell.value = value;
if (stop) {
*stop = YES;
}
}
}
}];
}
You can't break from setFieldValue, but you can from allCells.
It's up to the method you're using that calls the block — allCells in this case — to provide a mechanism for stopping the loop. Usually, it's a parameter to the block.
If allCells is yours and you don't mind modifying it, you modify the block signature to take a pointer to a BOOL, initialized to YES, and check if the block modified it to NO.
(Note: You can break from a for in loop.)