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.
Related
I have an Int64 (long-long) uniqueID passed as a number in a JSON file.
{ "id1" : 4627596562884205957 }
The NSJSONSerialization always returns an NSNumber (double) in the NSDictionary property. This should be an Int64 number NSNumber (long-long), as the double loses a few digits of needed precision.
It causes a failure, as the code is comparing the deserialized value (double) against the proper values (Int64), and for cases where the two numbers are very close, but still different, they compare as equal, which is the wrong logic.
Can NSJSONSerialization be configured to return the proper NSNumber(long-long) for this property, instead of the incorrect double?
Is it OK to pull the long-long value out of a NSNumber(double), then force it back into a new NSNumber(long-long)? It seems to work, but I don't trust it.
The following shows the error.
- (void) doJSONDoubleVsInt64Comparison
{
// our primary Int64
int64_t aId1 = 4627596562884205957;
// put it into a JSON string, then read it into a dictionary.
NSString *jsonInputString = [NSString stringWithFormat:#"{ \"id1\" : %lld }", aId1];
NSData *jsonData = [jsonInputString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:false];
NSDictionary *asDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:nil];
// the number after being deserialized from the JSON string
NSNumber *id1Number = [asDictionary objectForKey:#"id1"];
// the NSNumber is of type 'double' (not long-long as expected)
[self logNSNumber:id1Number named:#"id1"];
// get a DIFFERENT Int64 that varies only slightly
int64_t aId2 = 4627596562884205592;
NSNumber *id2Number = [NSNumber numberWithLongLong:aId2];
// This is of type Int64 (long-long) as expected.
[self logNSNumber:id2Number named:#"id2"];
// id1 and id2 are not equal numbers, but this comparison returns TRUE - why?
if( [id1Number isEqualToNumber:id2Number] )
{
// we incorrectly hit this condition.
NSLog(#"ERROR: id1 and id2 were reported to be equal, but they are different numbers.");
}
else{
NSLog(#"OK: id1 and id2 are not equal, as expected.");
}
// pull the long-long from the double - force it to be an Int64
NSNumber *id1NumberAsInt64 = [NSNumber numberWithLongLong:id1Number.longLongValue];
// the comparison now works.
[self logNSNumber:id1NumberAsInt64 named:#"id1AsInt64"];
if( [id1NumberAsInt64 isEqualToNumber:id2Number] )
{
NSLog(#"ERROR: id1 and id2 were reported to be equal, but they are different.");
}
else{
NSLog(#"OK: id1 and id2 are not equal, as expected.");
}
}
- (void)logNSNumber:(NSNumber *)aNumber named:(NSString *)name
{
CFNumberType numberType = CFNumberGetType((CFNumberRef)aNumber);
switch ( numberType )
{
case kCFNumberSInt64Type:
NSLog(#"%# returned as an Int64", name); // does NOT hit this
break;
case kCFNumberDoubleType:
NSLog(#"%# returned as a double", name); // hits this
break;
default:
NSLog(#"%# NumberType = %ld", name, numberType );
break;
}
}
I am quite confused about one error I am receiving when saving an object. I am receiving the following error (when I print out the detailed description):
2015-05-08 08:19:51.589 Br[11240:208443] Core Data Save Error
NSValidationErrorKey
activityObject
NSValidationErrorPredicate
(null)
NSValidationErrorObject
<BRActivity: 0x7deb2aa0> (entity: BRActivity; id: 0x7deb2780 <x-coredata:///BRActivity/t86C0E8CD-2B6C-4AF7-986A-4797B7BEFDF5> ; data: {
activities = (
);
activityObject = nil;
activityType = 0;
body = "Someone left a comment on your post.";
timestamp = "2015-05-08 04:24:46 +0000";
uuidString = "c6a06b45-e2d5-45bd-9f64-20b13ac87526";
})
NSLocalizedDescription
The operation couldn’t be completed. (Cocoa error 1570.)
2015-05-08 08:19:51.590 Br[11240:208443] Core Data Save Error
So from looking on the internet, it seems to be a relationship problem. So a Br post has a relationship called activities relationship, the inverse of which is an activity object. Now that, as we can see from the error, is nil. So what kind of solution are we looking at here... Is there a way to make the relationship "optional" (ok to be nil!) or should I add an activity object? I really don't want to break anything here so if there's a subtle solution let me know. Thanks a bunch guys!
Also here is the method surrounding the save:
- (void)importArray:(NSArray *)array entityName:(NSString *)entityName attributeName:attributeName error:(NSError *__autoreleasing *)error {
NSParameterAssert(array);
NSParameterAssert(entityName);
[self.context performBlockAndWait:^{
for (NSDictionary *jsonDictionary in array) {
NSManagedObject *managedObject = [NSManagedObject upsertWithContext:self.context entityName:entityName dictionary:jsonDictionary attributeName:attributeName error:error];
if (nil == managedObject) {
if ([self.context hasChanges]) {
[self.context rollback];
}
return;
}
}
if ([self.context hasChanges]) {
if (![self.context save:error]) {
NSError *err = *error;
NSDictionary *userInfo = [err userInfo];
if ([userInfo valueForKey:#"NSDetailedErrors"] != nil) {
// ...and loop through the array, if so.
NSArray *errors = [userInfo valueForKey:#"NSDetailedErrors"];
for (NSError *anError in errors) {
NSDictionary *subUserInfo = [anError userInfo];
subUserInfo = [anError userInfo];
// Granted, this indents the NSValidation keys rather a lot
// ...but it's a small loss to keep the code more readable.
NSLog(#"Core Data Save Error\n\n \
NSValidationErrorKey\n%#\n\n \
NSValidationErrorPredicate\n%#\n\n \
NSValidationErrorObject\n%#\n\n \
NSLocalizedDescription\n%#",
[subUserInfo valueForKey:#"NSValidationErrorKey"],
[subUserInfo valueForKey:#"NSValidationErrorPredicate"],
[subUserInfo valueForKey:#"NSValidationErrorObject"],
[subUserInfo valueForKey:#"NSLocalizedDescription"]);
}
}
NSLog(#"Error: %#", err.localizedDescription);
}
return;
}
}];
}
Yes, relationships can be optional. Select the relationship and you will see the optional optional in the Data inspector pane on the top right.
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 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?
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.