ReactiveCocoa: block is never executed - objective-c

In my app, I have a download manager. After any of tasks is finished I need to get all data for tableView again and reload it. But I can't get data inside RACObserve signal. Here's my code.
NSArray *activeTasks = [[DownloadManager instance] tasksToProcess];
for (DownloadTask *task in activeTasks) {
[[[self
checkTask:task]
map:^(id value
return [self fetchDownloadedData];
}]
subscribeNext:^(NSArray *models) {
// models returns RACDynamicSignal not NSArray
NSLog(#"%#", models); // <RACDynamicSignal: 0x11611cb50> name:
NSLog(#"checktask next");
} completed:^{
// This is never being executed
NSLog(#"checktask completed");
}];
}
- (RACSignal *)checkTask: (DownloadTask *)task {
return [RACObserve(task, isFinished) map:^id(id _) {
return nil;
}];
}
- (RACSignal *)fetchDownloadedData {
return [[MyCoreDataModel fetchAll] flattenMap:^id(NSArray *models) {
// This is never being executed
return [models filter:^BOOL(MyCoreDataModel *model) {
return model.isDownloaded;
}];
}];
}
- (RACSignal *)fetchAll
{
return [[[MyCoreDataModel findAll] sortBy:#"title"] fetch];
}
Would be great if someone helps me getting where is my mistake is. Thanks in advance.

There are few mistakes:
In map function you use method which returns RACSignal - it's not correct. You should use flattenMap instead.
Complete block will never be called, because RACObserve - hot signal, it only sends next event.
I wrote small example, I hope it helps you.
NSMutableArray<RACSignal *> *signals = [NSMutableArray array];
for (DownloadTask *task in activeTasks) {
RACSignal *signal = [[RACObserve(task, isFinished) ignore:#NO] take:1];
[signals addObject:signal];
}
#weakify(self);
[[[RACSignal merge:signals] flattenMap:^RACStream *(id _) {
#strongify(self);
return [self fetchDownloadedData];
}] subscribeNext:^(NSArray *models) {
}];
Here I created array of signals. Each signal - observing the isFinished property. Also I added ignore:#NO] take:1]; - I think it's more right, because you only need YES value and after that no observe anymore (take:1). Then I merge these signals and each time when someone of them sends finished state, we fetch data.
Please, let me know if something is not understand, I try to explain more clearly.

Related

Use the variable after block successful completion

I'm working on a simple instagram project now.
for several occasions I've encountered the same problem.
To work with instagram I use InstagramKit Engine. It has some preset (void)s to make life easier. However I'm always stuck at the same problem.
Let's say we've got this:
- (void)getSelfUserDetails
{
[[InstagramEngine sharedEngine] getSelfUserDetailsWithSuccess:^(InstagramUser *userDetail) {
NSLog(#"%#",userDetail);
} failure:^(NSError *error) {
}];
}
Here userDetail is used inside of the "Success". And it works nice. What I need is to somehow save it after the block is done.
I've tried several things from creating a property to store the userDetails up to making my own method to return the userDetails. The same trouble with saving ints, NSStrings etc..
I think I'm missing some easy way out.
Show it to me please.
You need to capture an object in the block that you can send the response object.
This can be self.
-(void)processUserDetails:(InstagramUser *) userDetail
{
//....
}
- (void)getSelfUserDetails
{
[[InstagramEngine sharedEngine] getSelfUserDetailsWithSuccess:^(InstagramUser *userDetail) {
[self processUserDetails: userDetail];
} failure:^(NSError *error) {
}];
}
You could create a property to hold the Instagram user that you find and assign it to it in the success block:
#property (nonatomic) InstagramUser *myUser;
- (void)getSelfUserDetails
{
[[InstagramEngine sharedEngine] getSelfUserDetailsWithSuccess:^(InstagramUser *userDetail) {
NSLog(#"%#",userDetail);
self.myUser = userDetail;
} failure:^(NSError *error) {
}];
}
This is a good use for an NSNotification (since the call above happens asynchronously).
You could call it like:
[[NSNotificationCenter defaultCenter]
postNotificationName:#"TestNotification"
object:self
userInfo:#{ #"userDetail" : userDetail }
];
And then get that userInfo data back with whatever method is listening for that event.

Wait for two async methods to complete

I'd like to init a model, let the model do some async stuff and present a new viewcontroller once completed. But how do i wait for the two async methods to be completed and how do I setup the callback method?
Pseudocode
In my StartViewController.m:
-(void)openArticle
{
article = [Article initWithObject:someObject];
article.callback = changeView;
}
-(void)changeView
{
[self presentViewController:someController];
}
In my ArticleModel.m:
-(void)initWithObject:someObject
{
[self loadImage]
[self geoCode]
}
-(void)loadImage
{
runAsyncMethod: success:^() // This one is actually a AFNetworking setImageWithURLRequest
}
-(void)geoCode
{
runAnotherAsyncMethod: success:^() // This one is actually a geocodeAddressString operation
}
You can achieve this using dispatch_groups
- (void)initWithObject:(id)someObject
{
self = [super init];
if (self) {
self.dispatch_group = dispatch_group_create();
[self loadImage]
[self geoCode]
dispatch_group_notify(self.dispatch_group, dispatch_get_main_queue(), ^{
NSLog(#"Push new view controller");
});
}
return self;
}
- (void)loadImage
{
dispatch_group_enter(self.dispatch_group);
__weak __typeof(self) weakSelf = self;
runAsyncMethod: success:^{
__typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.dispatch_group) {
dispatch_group_leave(strongSelf.dispatch_group); // You need to ensure that this is called in both success and failure
}
}
}
- (void)geoCode
{
dispatch_group_enter(self.dispatch_group);
__weak __typeof(self) weakSelf = self;
runAnotherAsyncMethod: success:^{
__typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.dispatch_group) {
dispatch_group_leave(strongSelf.dispatch_group);
}
}
}
You do not wait. If you wait, it isn't asynchronous! You would be losing the entire point of asynchronous if you were to wait.
What you do is, when your success handler is called, you step out to the main thread (just in case you got called back on a background thread) and now do whatever you need to do. In other words, you just let your success handler get called whenever it happens to get called.
In your case, you might like to chain the things you want to do:
Call loadImage
In its callback, call geoCode
In its callback, step out to the main thread and present the new view controller.
You can use dispatch_group so that when a method is over, it just leaves the group. I use a similar code myself and it works like a charm.
- (void)initWithObject:someObject {
// Create a dispatch group
dispatch_group_t group = dispatch_group_create();
[self loadImageWithDispatchGroup:group];
[self geoCodeWithDispatchGroup:group];
// Here we wait for all the requests to finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// Do whatever you need to do when all requests are finished
});
}
- (void)loadImageWithDispatchGroup:(dispatch_group_t)group {
dispatch_group_enter(group);
runAsyncMethod: success:^() // This one is actually a AFNetworking setImageWithURLRequest
// In your success or failure AFNetworking method, call this as soon as the request ended
dispatch_group_leave(group);
}
- (void)geoCodeWithDispatchGroup:(dispatch_group_t)group {
dispatch_group_enter(group);
runAnotherAsyncMethod: success:^() // This one is actually a geocodeAddressString operation
// In your success async geocode callback method, call this as soon as the request ended
dispatch_group_leave(group);
}
I do not known your needs but native GCD way to wait several asynch tasks is
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_barrier_async

Return User's State as an NSString with reverseGeocodeLocation

I am trying to simply return a user's state. I understand that I need to use reverseGeocodeLocation. I would like to return the state as an NSString in the same way that I am returning the user latitude below:
- (NSString *)getUserLatitude
{
NSString *userLatitude = [NSString stringWithFormat:#"%f",
locationManager.location.coordinate.latitude];
return userLatitude;
}
I currently have this code, but I cannot get it to work. It may be because I am using (void). I am just a bit lost.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation
*)newLocation fromLocation:(CLLocation *)oldLocation {
CLGeocoder * geoCoder = [[CLGeocoder alloc] init];
[geoCoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks,
NSError *error) {
for (CLPlacemark * placemark in placemarks) {
NSString *userState = [placemark locality];
return userState;
}
}];
}
Anyone have any ideas? Thank you!
You have to do something with the retrieved locality in the completion block. This code is executed asynchronously long after the method (with void return) has returned itself.
Usually you would call some sort of method on your own view controller or model class that passes the retrieved information.
Replace the return userState, it does not match the return type of the block.
Instead put something like:
[myViewController didFinishGettingState:userState];
You should look into block and GCD basics so that you can appreciate how this asynchnonicity works.
You are probably not understanding the way your completionHandler is working. The reverseGeocodeLocation:completionHandler: takes an handler, which is a function that will be executed when the lookup is completed and invoked with the placemarks and error as parameters.
What you have to do is to perform something meaningful in that block.
I would start checking whether any error occurred, then I would call a method for failing or success as follows
[geoCoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks, NSError *error) {
if (error != nil) {
// Something bad happened...
[self didFailRetrievingUserState:error];
} else {
// Check whether the placemark retrieved is unique
if (placemarks.count > 1) {
NSMutableArray * states = [NSMutableArray array];
for (CLPlacemark * placemark in placemarks) {
NSString * userState = [placemark locality];
[states addObject:userState];
}
[self didFinishRetrievingUserStates:states];
} else {
[self didFinishRetrievingUserState:[placemarks[0] locality]];
}
}
}];
Then of course you need to implement the three methods we are calling in the block above
- (void)didFailRetrievingUserState:(NSError *)error {
// Show error
}
- (void)didFinishRetrievingUserStates:(NSArray *)userStates {
// Do something reasonable with the multiple possible values
}
- (void)didFinishRetrievingUserState:(NSString *)userState {
// Do something reasonable with the only result you have
}
Clearly the above code is meant as a suggestion. You can make different decisions, like for instance handling all the logic inside the handler block, or not discriminating between the unique/not unique cases.
In general it's just important that you understand that the handler block is not supposed to return anything since it's a void function. It's just supposed to do something, and this something may be invoking your "delegate" methods as defined in the example.

EXC_BAD_ACCESS during NSFileVersion call to removeOtherVersionsOfItemAtURL: inside coordinated write block

I'm using what seems to be a simple invocation of the NSFileVersion class method removeOtherVersionsOfItemAtURL: inside a coordinated writing block for some iCloud conflict resolution.
When my devices go into 'spaz mode', which is a technical term for repeatedly opening and closing the application on a few devices, an EXC_BAD_ACCESS exception is thrown internally. Code snippet:
- (void)compareVersionChanges:(NSFileVersion *)version {
if (![DataLoader iCloudPreferenceEnabled]) {
NSLog(#"Ignoring iCloud changes (version comparison) based on user preference");
return;
}
NSLog(#"compareVersionChanges");
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^(void) {
NSError *readError = nil;
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:(id)self];
[coordinator coordinateReadingItemAtURL:[version URL] options:0 error:&readError byAccessor:^(NSURL *newURL) {
DataContext *loadedContext = nil;
NSData *data = [NSData dataWithContentsOfURL:newURL];
NSError *e = nil;
loadedContext = [self convertXmlDataToContext:data error:&e];
if (e) {
NSLog(#"Done loading, error: %#", e);
[[DataLoader applicationDelegate] displayError:e];
loadedContext = nil;
}
if (!loadedContext) {
return;
}
id appDelegate = [DataLoader applicationDelegate];
DataContext *inMemoryContext = nil;
if (appDelegate != nil && [appDelegate respondsToSelector:#selector(context)]) {
inMemoryContext = [appDelegate performSelector:#selector(context)];
}
if (inMemoryContext) {
NSLog(#"Performing iCloud context synchronizating...");
DataContextSynchronizer *synchronizer = [[DataContextSynchronizer alloc] init];
ChangeSet *changes = [synchronizer compareLocalContext:inMemoryContext andRemoteContext:loadedContext];
if ([[changes changes] count] > 0) {
[SelectionManager disable];
#synchronized(appDelegate) {
NSLog(#"Applying synchronization changes...");
[synchronizer applyChangeSet:changes toDataContext:inMemoryContext];
NSLog(#"Synchronization changes applied");
}
[SelectionManager enable];
if ([appDelegate respondsToSelector:#selector(setSkipRefreshSave:)]) {
[appDelegate performSelector:#selector(setSkipRefreshSave:) withObject:[NSNumber numberWithBool:YES]];
}
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^(void) {
[SelectionManager notifyListeners];
});
if ([appDelegate respondsToSelector:#selector(setSkipRefreshSave:)]) {
[appDelegate performSelector:#selector(setSkipRefreshSave:) withObject:[NSNumber numberWithBool:NO]];
}
[self save:[[DataLoader applicationDelegate] context]];
} else {
NSLog(#"No sync changes applicable.");
}
NSError *coordinateWriteRemoveError = nil;
[coordinator coordinateWritingItemAtURL:newURL options:NSFileCoordinatorWritingForDeleting error:&coordinateWriteRemoveError byAccessor:^(NSURL *theURL) {
theURL = [theURL copy];
NSError *removeOtherVersionsError = nil;
[NSFileVersion removeOtherVersionsOfItemAtURL:theURL error:&removeOtherVersionsError];
if (removeOtherVersionsError) {
NSLog(#"Error removing other versions: %#", removeOtherVersionsError);
}
}];
if (coordinateWriteRemoveError) {
NSLog(#"Error occurred coordinating write for deletion of other file versions: %#", coordinateWriteRemoveError);
}
}
}];
if (readError) {
NSLog(#"Done loading (outside block) error: %#", readError);
}
});
}
I thought a little syntax highlighting might make this easier to examine:
Link to image of code snippet and failure stack in Xcode
The error actually occurs on line 1404, and as you can see from the below screenshot, it's deep in Apple code territory.
Link to image of debugger
Before submitting a radar, I thought I'd check here to see if there's something I'm doing wrong? The extra [... copy] on line 1402 was just a quick check to make sure I'm not losing the reference to the block-provided argument, and will be removed.
Edit: An important note! I'm using ARC.
Edit 2: I've noticed that when calling:
[NSFileVersion otherVersionsOfItemAtURL:theURL]
The return value is nil, which indicates (via the documentation):
...or nil if there is no such file. The array does not contain the version object returned by the currentVersionOfItemAtURL: method.
So by checking the return value of this method before I make the call to removeOtherVersionsOfItemAtURL:, it has alleviated the issue. But I still find it strange that an EXC_BAD_ACCESS is thrown, rather than that method handling it properly.
I've noticed that when calling:
[NSFileVersion otherVersionsOfItemAtURL:theURL]
immediately prior to the call to removeOtherVersionsOfItemAtURL:, the return value is nil, which indicates (via the documentation):
Returns: An array of file version objects or nil if there is no such
file. The array does not contain the version object returned by the
currentVersionOfItemAtURL: method.
So by checking the return value of this method before I make the call to removeOtherVersionsOfItemAtURL:, it has alleviated the issue. But I still find it strange that an EXC_BAD_ACCESS is thrown by removeOtherVersionsOfItemAtURL:, rather than that method simply returning NO, or simply populating the provided NSError object.
I'll be filing a Radar and will update here when I hear back.

Synchronizing a Block within a Block?

I'm playing around with blocks in Objective-C, trying to come up with a reusable mechanism that will take an arbitrary block of code and a lock object and then execute the block of code on a new thread, synchronized on the provided lock. The idea is to come up with a simple way to move all synchronization overhead/waiting off of the main thread so that an app's UI will always be responsive.
The code I've come up with is pretty straightforward, it goes like:
- (void) executeBlock: (void (^)(void))block {
block();
}
- (void) runAsyncBlock: (void (^)(void))block withLock:(id)lock {
void(^syncBlock)() = ^{
#synchronized(lock) {
block();
}
};
[self performSelectorInBackground:#selector(executeBlock:) withObject:syncBlock];
}
So for example, you might have some methods that go like:
- (void) addObjectToSharedArray:(id) theObj {
#synchronized(array) {
[array addObject: theObj];
}
}
- (void) removeObjectFromSharedArray:(id) theObj {
#synchronized(array) {
[array removeObject: theObj];
}
}
Which works fine, but blocks the calling thread while waiting for the lock. These could be rewritten as:
- (void) addObjectToSharedArray:(id) theObj {
[self runAsyncBlock:^{
[array addObject: theObj];
} withLock: array];
}
- (void) removeObjectFromSharedArray:(id) theObj {
[self runAsyncBlock: ^{
[array removeObject: theObj];
} withLock:array];
}
Which should always return immediately, since only the background threads will compete over the lock.
The problem is, this code crashes after executeBlock: without producing any output, error message, crash log, or any other useful thing. Is there something fundamentally flawed in my approach? If not, any suggestions with respect to why this might be crashing?
Edit:
Interestingly, it works without crashing if I simply do:
- (void) runAsyncBlock: (void (^)(void))block withLock:(id)lock {
void(^syncBlock)() = ^{
#synchronized(lock) {
block();
}
};
syncBlock();
}
But of course this will block the calling thread, which largely defeats the purpose. Is it possible that blocks do not cross thread boundaries? I would think not, since that would largely defeat the purpose of having them in the first place.
remember to call [block copy] otherwise it is not correctly retained because block are created on stack and destroyed when exit scope and unless you call copy it will not move to heap even retain is called.
- (void) runAsyncBlock: (void (^)(void))block withLock:(id)lock {
block = [[block copy] autorelease];
void(^syncBlock)() = ^{
#synchronized(lock) {
block();
}
};
syncBlock = [[syncBlock copy] autorelease];
[self performSelectorInBackground:#selector(executeBlock:) withObject:syncBlock];
}