ReactiveCocoa nested map - ios

[[[[[self.textfield.rac_textSignal throttle:0.5] flattenMap:^RACStream *(id value) {
//call api
return [API signal];
}] flattenMap:^RACStream *(NSArray *result) {
result = [result.rac_sequence take:150].array;
//result is json array
return result;
}] map:^id (NSArray *result) {
return [result.rac_sequence map:^id (JSON *r) {
//handle json item
return item;
}].array;
}] subscribeNext:^(NSArray *result) {
//reload ui
} error:^(NSError *error) {
} completed:^{
}];
How to avoid the nested map in the signal sequence?
It is a better way to handle this?

Related

Get RACTuple from merged signals

I don't have any idea how to merge a lot of signals and get results from a RACTuple, its seems to be like easy answer but I can't found that.
What we have for exmaple:
NSArray *a = #[#{#"k1":#"v1"},
#{#"k2":#"v2"},
#{#"k3":#"v3"},
#{#"k4":#"v4"},
#{#"k5":#"v5"},
#{#"k6":#"v6"},
#{#"k7":#"v7"}];
NSArray *b = #[#{#"kk1":#"vv1"},
#{#"kk2":#"vv2"},
#{#"kk3":#"vv3"},
#{#"kk4":#"vv4"},
#{#"kk5":#"vv5"},
#{#"kk6":#"vv6"},
#{#"kk7":#"vv7"}];
and
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
RACSignal *s1 = [self adaptObjects:a];
RACSignal *s2 = [self adaptObjects:b];
return [[RACSignal merge:#[s1,s2]] map:^id(id value) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:value];
return nil;
}];
}];
}];
[[command execute:nil] subscribeNext:^(RACTuple *x) {
NSLog(#"%#",x);
}];
this operator map is wrong I know that, but this is for example
- (RACSignal *)adaptObjects:(NSArray *)objects {
return [objects.rac_sequence.signal flattenMap:^RACStream *(id x) {
return [self adaptObject:x];
}];
}
- (RACSignal*)adaptObject:(NSDictionary*) x {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// some operations with data here
[subscriber sendNext:x];
return nil;
}];
}
In NSLog I want to see tuple result with two arrays first - s1, second - s2
Thx
I've written a small example, hope it helps you.
NSArray *a = #[#{#"k1":#"v1"},
#{#"k2":#"v2"},
#{#"k3":#"v3"},
#{#"k4":#"v4"},
#{#"k5":#"v5"},
#{#"k6":#"v6"},
#{#"k7":#"v7"}];
NSArray *b = #[#{#"kk1":#"vv1"},
#{#"kk2":#"vv2"},
#{#"kk3":#"vv3"},
#{#"kk4":#"vv4"},
#{#"kk5":#"vv5"},
#{#"kk6":#"vv6"},
#{#"kk7":#"vv7"}];
- (NSArray<RACSignal *> *)rac_signalsFromArray:(NSArray *)array {
NSMutableArray<RACSignal *> *signals = [NSMutableArray array];
for (NSDictionary *dict in array) {
[signals addObject:[RACSignal return:dict]];
}
return signals;
}
NSArray *Asignals = [self rac_signalsFromArray:a];
NSArray *Bsignals = [self rac_signalsFromArray:b];
NSArray *signals = [[NSArray arrayWithArray:Asignals] arrayByAddingObjectsFromArray:Bsignals];
[[RACSignal zip:signals] subscribeNext:^(RACTuple *tuple) {
// tuple here
}];

RACSignal from rac_sequance is not delivering signals correctly

I am playing with code from some book regarding Reactive Cocoa and I am stuck with this one:
Here is my photo importer code :
+ (RACSignal *)importPhotos {
NSURLRequest *request = [self popularURLRequest];
return [[[[[[NSURLConnection rac_sendAsynchronousRequest:request] map:^id(RACTuple *value) {
return [value second];
}] deliverOn:[RACScheduler mainThreadScheduler]]
map:^id(NSData *value) {
id result = [NSJSONSerialization JSONObjectWithData:value
options:0
error:nil];
return [[[result[#"photos"] rac_sequence] map:^id(NSDictionary *photoDictionary) {
FRPPhotoModel *photoModel = [FRPPhotoModel new];
[self configurePhotoModel:photoModel withDict:photoDictionary];
[self downloadThumbnailForPhotoModel:photoModel];
return photoModel;
}] array];
}] publish] autoconnect];
}
+ (void)configurePhotoModel:(FRPPhotoModel *)photoModel withDict:(NSDictionary *)dict {
photoModel.photoName = dict[#"name"];
photoModel.identifier = dict[#"id"];
photoModel.photographerName = dict[#"user"][#"username"];
photoModel.rating = dict[#"rating"];
[[self urlForImageSize:3 inArray:dict[#"images"]] subscribeNext:^(id x) {
photoModel.thumbnailURL = x;
}];
}
+ (RACSignal *)urlForImageSize:(NSInteger)size inArray:(NSArray *)array {
return [[[[array rac_sequence] filter:^BOOL(NSDictionary *value) {
return [value[#"size"] integerValue] == size;
}] map:^id(NSDictionary *value) {
return value[#"url"];
}] signal];
}
+ (void)downloadThumbnailForPhotoModel:(FRPPhotoModel *)photoModel {
[[RACObserve(photoModel, thumbnailURL) flattenMap:^RACStream *(id value) {
return [self download:value];
}] subscribeNext:^(id x) {
photoModel.thumbnailData = x;
}];
}
+ (RACSignal *)download:(NSString *)urlString {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
return [[NSURLConnection rac_sendAsynchronousRequest:request] map:^id(RACTuple *value) {
return [value second];
}];
}
and UI is updated like this:
RAC(self.imageView, image) = [[RACObserve(self, photoModel.thumbnailData) filter:^BOOL(id value) {
return value != nil;
}] map:^id(id value) {
return [UIImage imageWithData:value];
}];
Can you please explain why my UI is not updated or updated wrongly with new UIImages which I get from that NSData objects.
So the first problem was that flattenMap delivers on some background RACScheduler. Changed to this:
[[[[RACObserve(photoModel, thumbnailURL) ignore:nil] flattenMap:^RACStream *(id value) {
return [self download:value];
}] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
photoModel.thumbnailData = x;
}];
Also another problem was that download:nil was throwing an error, which was not caught by subscriber and thus terminated signal which provided values of observing. Adding ignore:nil fixed issue.

iOS - Parse: findObjectsInBackgroundWithBlock with completion

I'm fetching data using findObjectsInBackgroundWithBlock but I have to use the data when all of it is gathered. I don't know how to implement a callback or anything which would alert me once all the data is retrieved and ready for me to use. Is there any way to get alerted?
Code:
[query findObjectsInBackgroundWithBlock:^(NSArray* array, NSError* error)
{
if(!error)
{
if([array count] > 0)
{
PFObject* relationship = [array objectAtIndex:0];
if([relationship.objectId length] > 0)
{
if([[relationship objectForKey:#"initiatedBy"] isEqualToString:parseID]) // relationship initiated by current user -youLike
{
[youLike addObject:relationship];
NSLog(#"youLike added");
}
else
{
[likeYou addObject:relationship];
NSLog(#"likeYou added");
}
}
else NSLog(#"Custom error when cycling through user relationships: objectId is nil");
}
else
{
NSLog(#"Custom error when cycling through user relationships: Relationship at index %d could not be found in database", i);
}
}
else
{
NSLog(#"Error when cycling through user relationships: %#", error);
}
}
You need to use Blocks
Declare the Block (Here you choose your block types)
typedef void (^YourBlock)(NSArray *array, NSError *error);
Add the block and the findObjectsInBackgroundWithBlock in a method.
- (void) yourMethod:(YourBlock)block {
[query findObjectsInBackgroundWithBlock:^(NSArray *array, NSError *error) {
//Do your things here if you want change something.
//For instance: convert types, convert errors.
block(array, error); //Is called when every thing is retrieve.
}
}
Call your method in the Application
SomeClass *someClass = [SomeClass alloc]init];
[[object getScoresFromParse:^(NSArray *array, NSError *error) {
//Everything complete here.
}];
You can use blocks.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[query findObjectsInBackgroundWithBlock:^(NSArray* array, NSError* error)
{
if(!error)
{
if([array count] > 0)
{
PFObject* relationship = [array objectAtIndex:0];
if([relationship.objectId length] > 0)
{
// It will be called when Parse finishes.
dispatch_async(dispatch_get_main_queue(), ^{
if([[relationship objectForKey:#"initiatedBy"] isEqualToString:parseID]) // relationship initiated by current user -youLike
{
[youLike addObject:relationship];
NSLog(#"youLike added");
}
else
{
[likeYou addObject:relationship];
NSLog(#"likeYou added");
}
// [...yourcode...]
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Custom error when cycling through user relationships: objectId is nil");
});
}
}
}
}];
}];

ReactiveCocoa flattenMap not called

I have two data sources that are pulling in different arrays of both Contacts and Users and an aggregate Invitee datasource which is created to combine and keep references to the results of the Contacts and Users:
AddressBookDataSource:
- (RACSignal *)getContacts {
return [[[[self getContactsSignal] flattenMap:^RACStream *(NSArray *contacts) {
return contacts.rac_sequence.signal;
}]
map:^id(APContact *contact) {
return [[Contact alloc] initWithAPContact:contact];
}] collect];;
}
- (RACSignal*)getContactsSignal {
APAddressBook *addressBook = [[APAddressBook alloc] init];
addressBook.fieldsMask = APContactFieldFirstName | APContactFieldCompositeName | APContactFieldPhoto;
RACSignal *addressBookSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[addressBook loadContacts:^(NSArray *contacts, NSError *error) {
if (error) {
[subscriber sendError:error];
} else {
[subscriber sendNext:contacts];
}
}];
return nil;
}];
return addressBookSignal;
}
ParseDataSource:
- (RACSignal *)getUsers {
return [[[[[self getUsersSignal] flattenMap:^RACStream *(NSArray *users) {
return users.rac_sequence.signal;
}] filter:^BOOL(User *user) {
return ![user.username isEqualToString:[User currentUser].username];
}] map:^id(User *user) {
return user;
}] collect];
}
- (RACSignal*)getUsersSignal {
RACSignal *getUsersSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
PFQuery *userQuery = [User query];
[userQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
[subscriber sendError:error];
} else {
[subscriber sendNext:objects];
}
}];
return nil;
}];
return getUsersSignal;
}
InviteeDataSource:
- (RACSignal*)getPotentialInvitees {
ParseDataSource *parseDataSource = [[ParseDataSource alloc] init];
AddressBookDataSource *addressBookDataSource = [[AddressBookDataSource alloc] init];
return [[RACSignal concat:#[
[parseDataSource getUsers],
[addressBookDataSource getContacts]
]]
flattenMap:^RACSignal *(RACTuple *tuple) {
RACTupleUnpack(NSArray *users, NSArray *contacts) = tuple;
_contactSection.contacts = contacts;
_userSection.users = users;
return [RACSignal empty];
}];
}
The problem is that the flattenMap block never gets called, meaning the subsequent subscribers never have their subscribeNext blocks called.
Help?
Thanks to a twitter reply from #jspahrsummers, there were a couple things that were making this not behave as I'd like, but the root of the problem seemed to be that I was not calling -sendCompleted on the subscriber in the -getUsersSignal and -getContactsSignal.

Ordering operations in ReactiveCocoa with concurrent delegate callbacks

I am still trying to wrap my head around ReactiveCocoa, and one thing in particular is causing headaches. I would like to have a declarative interface to the CoreBluetooth API that lets me do something like this:
#property (nonatomic) NSNumber *registerValue;
#property (nonatomic) NSNumber *serialNumber;
RACSignal *characteristics = [[RACObserve(self, connectedPeripheral) flattenMap:^RACStream *(CBPeripheral *peripheral) {
return [peripheral rac_discoverServices:#[[CBUUID UUIDWithString:kBLEServiceUUIDString]]];
}] flattenMap:^RACStream *(CBService *service) {
return [service rac_discoverCharacteristics:nil];
}];
RACSignal *devInfoCharacteristics = [[RACObserve(self, connectedPeripheral) flattenMap:^RACStream *(CBPeripheral *peripheral) {
return [peripheral rac_discoverServices:#[[CBUUID UUIDWithString:kDevInfoServiceUUIDString]]];
}] flattenMap:^RACStream *(CBService *service) {
return [service rac_discoverCharacteristics:nil];
}];
RACSignal *registerUpdates = [[[characteristics filter:^BOOL(CBCharacteristic *characteristic) {
return ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kRegisterCharacteristicUUIDString]]);
}] flattenMap:^RACStream *(CBCharacteristic *characteristic) {
return [[characteristic rac_updateNotificationState:YES] mapReplace:characteristic];
}] flattenMap:^RACStream *(CBCharacteristic *characteristic) {
return [characteristic rac_didUpdateValue];
}];
RACSignal *serialNumberSignal = [[[characteristics filter:^BOOL(CBCharacteristic *characteristic) {
return ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kSerialNumberCharacteristicUUIDString]]);
}] flattenMap:^RACStream *(CBCharacteristic *characteristic) {
return [characteristic rac_didUpdateValue];
}];
RAC(self, serialNumber) = serialNumberSignal;
RAC(self, registerValue) = registerUpdates;
Now, the implementation of rac_discoverServices looks something like this (a category on CBPeripheral):
- (RACSignal *)rac_discoverServices:(NSArray *)serviceUUIDs
{
#weakify(self);
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
#strongify(self);
RACDisposable *disposable = [[[[self rac_didDiscoverServices] take:1] flattenMap:^RACStream *(id value) {
return [[[self.services rac_sequence] signal] filter:^BOOL(CBService *service) {
if (!serviceUUIDs) {
return YES;
} else {
for (CBUUID *uuid in serviceUUIDs) {
if ([uuid isEqual:service.UUID]) {
return YES;
}
}
return NO;
}
}];
}] subscribe:subscriber];
[self discoverServices:serviceUUIDs];
return disposable;
}];
}
The problem is that I don't think this can work, because I don't believe the callback ordering can be guaranteed. So what I really want is to be able to ensure that the subscription to the second signal cannot issue the call to discover new services until the first signal has completed.
I understand that RACCommand will prevent concurrent operation, but I don't see a way to use it to ensure that the operations are queued.
Is there some obvious solution I am missing? The only thing I can think of is to somehow maintain a queue of calls and call discover again when the prior signal is disposed of (and I'm not certain that would work either).

Resources