In for Loop, inside GCD, pause, get input, continue - objective-c

I want to know if there was a way to break away from gcd, show in input alert view and then return to the process? THis was my normal routine:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSMutableArray *array = [NSMutableArray new];
for(NSIndexPath * ip in [self.tableView indexPathsForSelectedRows]){
[array addObject:[CompanyObjectsArray objectAtIndex:ip.row]];
}
//I need to check for a an Object here, if present get input from User.
[self addCompaniesOrCreate:[NSArray arrayWithArray:array]];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
I add all objects into core data like that,
But I had a need where'd if a company is "Appl3" , i should get an input from the User, and if i get it, continue and loop with the rest, or else skip the current "Appl3" company.
Is there a way i could do this efficiently, while in the for() loop, pause, get input, continue the for() loop.
thanks for the help guys :)

There is no such way I am aware of. The usual pattern would be to write your own method that takes a block as a completion handler. So basically you pass back to the main thread the block that you want to run when you've got your user input you wanted.
Best regards,
sven.

Related

NSString gets corrupted, changes to NSPrincipalClass

I have an NSDragOperation that gets a property lists path upon the user dragging it into the window. That seems to work just fine, and I can save the path information to an NSString:
NString *thisPath = draggedFilePath;
NSLog(#" %#",thisPath);
output: 2014-02-09 09:19:46.072 app[5944:303] /Users/Me/Desktop/file.plist
The problem starts when I go into a dispatch queue. When I try and read the NSString from inside the background queue the output becomes NSPrincipalClass. Does anyone know why this is happening, or if I'm supposed to convert the NSString to some other format before entering dispatch_queue_t?
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
for (NSUInteger i = 0; i < 1; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
});
}
NSLog(#" %#",thisPath);
output: 2014-02-09 09:19:56.234 app[5944:12203] NSPrincipalClass
EDIT: the output of the string using NSLog inside dispatch queue turns up random symbols, and also crashes. The last output was <__NSMallocBlock__: 0x55a860> - I have other strings that seem to be just fine within the same function, so I really don't know what is causing this.
one of three things is happening...
it is being deallocated, and a new object is being allocated in its place... you can try zombies...
it is getting assigned to a garbage value ie. draggedFilePath isn't ever initialized to zero, and isn't set to a good value.
your stack is getting smashed and it just happens to be there when it crashes... this is the hardest to find.
you are going to have to turn on zombies, the exception breakpoint and just step through it in the debugger... if that fails you get to either run in instruments with the malloc tool or turn on malloc history logging.
Does it work if you prefix the declaration of thisPath with `__block'? Like this:
__block NString *thisPath = draggedFilePath;

AVQueuePlayer playback freezes when removing items from queue

I successfully use AVQueuePlayer to queue and play AVPlayerItems. However, the problems begin when I alter the players queue with calls to removeItem: method. My assumption was that I can freely modify this queue as long as I don't touch the currently played item, but it seems that each call to removeItem: freezes the playback for a moment.
Consider the following code snippet:
+ (void)setBranch:(BranchType)bType {
NSArray* items = [dynamicMusicPlayer items];
for (AVPlayerItem* item in [items copy]) {
if (item == dynamicMusicPlayer.currentItem) {
continue; // don't remove the currently played item
}
[dynamicMusicPlayer removeItem:item];
}
[self queueNextBlockFromBranchType:bType];
currentBranch = bType;
}
You can guess from this, that it's a dynamic music player that plays the music from different branches. So basically, when I comment the line where I remove items, it plays all ok, but obviously the branch is not changed as soon as I want it to. The freeze occurs exactly at the time the removing happens, so the currently played item is being interrupted, but the transition between the actual items is seamless. Also note that I never have more than 2 items in the queue, so in the loop I basically remove one item only.
So, my question is: is there any way to avoid this freeze? And what is causing the freeze on the first place?
Edit So for the people who encountered the same problem, the solution for me was to stop using AVFoundation and use OrigamiEngine instead.
Instead of removing the item in the loop, you could consider scheduling an NSOperation to do it later.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// do something here
}];
Ex:
+ (void)setBranch:(BranchType)bType {
NSArray* items = [dynamicMusicPlayer items];
for (AVPlayerItem* item in [items copy]) {
if (item == dynamicMusicPlayer.currentItem) {
continue; // don't remove the currently played item
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[dynamicMusicPlayer removeItem:item];
}];
}
[self queueNextBlockFromBranchType:bType];
currentBranch = bType;
}
I don't know if this will help, but it'll at least shift the actual item removal to a scheduled operation that the device can decide when to run.
This is a much hackier solution, but it might help.
Rather than removing the item from dynamicMusicPlayer immediately, you could mark it for removal by adding it to a separately maintained list of things to remove.
Then, when the AVQueuePlayer is about to advance to the next item, sweep through your list of deletable items and take them out. That way the blip occurs in the boundary between items rather than during playback.

Objective-C: UIImageWriteToSavedPhotosAlbum() + asynchronous = problems

Update: This problem was also reported here, with a more detailed treatment of the cause:
UIImageWriteToSavedPhotosAlbum saves only 5 image out of 10. Why?
In my case as well, the error was: "Write busy" - this seems to be an issue related to device speed. There is probably some solution that involves manually handling threading or similar - but, inspired by Tommy's answer below, I serialized the saving of images, and that works around the problem.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Situation:
I'm trying to save a large-ish collection of images to the camera roll using a for-loop (number of images depends on user input - may be 1, 2, 3, or theoretically hundreds). Images are pointed to by an NSSet (though I could just as easily do an NSArray), and the NSSet is set to nil after the for-loop completes.
I'm using UIImageWriteToSavedPhotosAlbum() to save out the images on a detached thread (using NSThread's detachNewThreadSelector:toTarget:withObject: class method), and starting a UIActivityIndicator spinner on the main thread.
Problem:
When I attempt to save out more than ~5, any image after the ~5th will output this error in the log:
*** -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL
For example, let's say I exported 9 images. The for-loop will run through all 9 images (confirmed by NSLogging), but I'll get around 4 iterations of the error above (and only 5 images saved to the camera roll).
If I add a breakpoint in the loop and wait a second or two in between each iteration, however, they are all saved correctly without complaint. So..
Theory:
Based on my logs and observations, UIImageWriteToSavedPhotosAlbum() is clearly running asynchronously and is somehow 'too slow' to keep up with my application.
Is there a simple way to force it to run synchronously (ideally on the main thread)? I've been experimenting with adding reference counts to the images I'm trying to save out, but (1) this feels hacky, and (2) I haven't solved the problem regardless.
Any advice would be great. Thanks!
I use ALAssetsLibrary and a dispatch_queue to make sure that images are saved sequentially:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
dispatch_queue_t queue = dispatch_queue_create("com.myApp.saveToCameraRoll", NULL);
[images enumerateObjectsUsingBlock:^(UIImage *image, NSUInteger idx, BOOL *stop) {
dispatch_async(queue, ^{
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[library writeImageToSavedPhotosAlbum:image.CGImage metadata:metaData completionBlock:^(NSURL *assetURL, NSError *writeError) {
if (writeError) {
// handle the error
}
else {
if (image == [images lastObject]) {
dispatch_async(dispatch_get_main_queue(), ^{
// perhaps indicate to the user that save has finished
});
}
}
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
});
}];
If you're desperate not to write multiple images at once then you could use UIImageWriteToSavedPhotosAlbum's completion target and selector to implement a form of tail recursion. Something like:
- (void)writeSetToSavedPhotosAlbum:(NSMutableSet *)images
{
if(![images count]) return;
UIImage *imageToSave = [[[images anyObject] retain] autorelease];
[images removeObject:imageToSave];
NSLog(#"I shall now write image %#", imageToSave);
UIImageWriteToSavedPhotosAlbum(
imageToSave,
self,
#selector(writeSetToSavedPhotosAlbum:),
images);
}
EDIT: it may also be worth seeing whether you get the same results with ALAssetsLibrary's -writeImageToSavedPhotosAlbum:orientation:completionBlock:, which takes a block for completion so is even more straightforward to work with.
The docs do mention you'll be notified asynchronously. I wonder if this might work: for each image you want to save create a block operation and add it to main operation queue. Kind of like:
for (UIImage *toSave in imagesToSave)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImageWriteToSavedPhotosAlbum(toSave, target, selector, ctx);
}];
}
Might be worth a shot, let me know if it helps!
In this Code Work Save image :
UIImageWriteToSavedPhotosAlbum([self screenshot], nil, nil, nil);
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Save" message:#"Photo saved to album" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];

how do dispatch queues work

Im a bit confused here, Im using queues and I got to a point where Im a bit lost.
I have a method named getPeople who has to fetch pictures of users from the server. In order not to block the app I used this:
-(IBAction)seeMorePeople{
dispatch_queue_t getPeopleQueue = dispatch_queue_create("Pinta Ocupantes", NULL);
dispatch_async(getPeopleQueue, ^{
[self getPeople];
});
dispatch_release(getPeopleQueue);
}
The previous code is executed everytime the user taps a button. Something like "Give me pics from this album" and then another tap "Now I want people's pictures from that other album", diferent pics and different amount of pictures. If the user taps the buttons quite fast, the first queue wont finish fetching the data when the second one is already starting. With in getPeople I store the data in an NSMutableArray, so when the 2 queues are executing at the same time both are writing on the same Array and the app crashes due to out of bounds exception.
The way getPeople goes through the data is something like this:
-(void)getPeople:(NSDictionary *)peopleDictionary{
//I receive an NSDictionary and I go through it
NSArray *keys = [peopleDictionary allKeys];
int indexOfArray = 0;
for(NSString *key in keys){
//Complex operation that are not important
[peopleInArray insertObjetAtIndex:indexOfArray];//People in array is a global variable
indexOfArray++;
}
}
What I can't figure out is how to get out of this, I thought of stopping the first queue when the second one comes in, but GCD doesnt have this option... any other way to get this done, hopefully without a major recoding, anyway right now Im out of ideas, so any clue will help.
I would suggest that you avoid synchronizing with semaphores, if possible. The design of GCD is to avoid that. A background task should prepare data but not touch outside state. When the data is prepared, it should dispatch the updating of outside state to either a serial queue or, if the state is bound to the GUI, to the main queue.
So, something like this:
-(void)getPeople:(NSDictionary *)peopleDictionary{
//I receive an NSDictionary and I go through it
NSArray *keys = [peopleDictionary allKeys];
for(NSString *key in keys){
//Complex operation that are not important
dispatch_async(dispatch_get_main_queue(), ^{
[peopleInArray addObject:<whatever>];
});
}
}
If you rather want to replace the array, instead of having two threads adding to it in interleaved fashion, you'd accumulate the whole array in the background and dispatch setting the entirety of peopleInArray to the main queue.
If you want cancellation, you can implement it yourself with a flag, or you should maybe consider using NSOperation and NSOperationQueue instead of GCD. Those have a concept of cancellation built in, although your custom operations still need to check if they've been cancelled and stop working.
You are right, there is no way to stop a queue which was dispatched. One thing you could do to make sure that only one queue is accessing getPeople at the same time is using semaphores (this might be even more interesting).
If you just want to avoid that the users clicks the button multiple times you could use a bool variable stillExecuting which is set to YES in your asynchronous dispatch and set to NO at the end of getPeople. Before creating getPeopleQueue you simply check if getPeople is still executing.
if(!stillExecuting) {
dispatch_queue_t getPeopleQueue = dispatch_queue_create("Pinta Ocupantes", NULL);
dispatch_async(getPeopleQueue, ^{
[self getPeople];
});
dispatch_release(getPeopleQueue);
}
NSString *lastLoginTime =#" Your last login time";
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate* date1 = [dateFormat lastLoginTime];
NSDate* date2 = [dateFormat dateFromString:[NSString currentDateTime]];
NSTimeInterval secondsSinceLastLogin = [date2 timeIntervalSinceDate:date1];
// NSLog(#"Seconds Since Last Login: %g", secondsSinceLastLogin);
int hours = (int)secondsSinceLastLogin / 3600;

Perform action after label is updated in ios

I am using a pin screen for login to my app. The pin screen consists of four labels and a hidden text field. When the user enters text via the keypad, I update the labels with a symbol. This works fine, except that the last label does not get actually get updated before login begins, and remains empty while the login process is completed.
These are the relevant bits of code:
//an observer has been added elsewhere
- (void)textDidChange:(NSNotification *)notification
{
UITextField *field = [notification object];
if (field == inputField)
{
NSString *newText = field.text;
if ([newText length] <= pinLength) [self updatePINDisplay];
}
}
-(void)updatePINDisplay
{
if ([pinText length] > pinLength) return;
for (NSInteger ii = 0; ii < [pinText length]; ii++)
{
UILabel *label = [pinFields objectAtIndex:ii];
[label setText:#"x"];
}
for (NSInteger ii = [pinText length]; ii < pinLength; ii++)
{
UILabel *label = [pinFields objectAtIndex:ii];
[label setText:[NSString string]];
}
if ([pinText length] == pinLength) [self login];
}
The problem arises because [self login] launches other processes which happen before the last pin label is updated, so the login occurs while the last box is still empty.
I have worked around the problem by replacing
[self login]
with
[self performSelector:#selector(login) withObject:nil afterDelay:0.1]
but I don't like the arbitrary time delay. I was hoping that maybe there was a delegate method that I could use to launch my login code after the label has been drawn. Something like:
-(void)labelDidGetDrawn
Any other (non-hack) solution is also welcome:-)
Thanks!
Based on your description, it sounds like the problem is that the 4th item doesn't get drawn until after the [self login] finishes, which is indicative that the login procedure takes some time. In iOS, drawing doesn't happen immediately, which is why you're only getting the draw if you defer the login until after the OS has an opportunity to update the display.
You have used one reasonable solution here. Another (arguably less of a hack) is to have your -[self login] spawn the login on a separate thread, or at least using an asynchronous mechanism (such as the asynchronous modes of NSURLConnection, assuming you're making a network request). Then your main thread will quickly return control to iOS and your box will draw.
With Grand Central Dispatch, you could do most of this by having the -[self login] place the network code on a background thread, and have the background thread call back to your main thread when complete. However, this can cause some problems if you want to respond to user events during the login process.
If you can, using NSURLConnection asynchronously, and setting up the delegate to report back to you when the operation is complete is probably the best choice, as it gives you the operation to cancel the NSURLConnection during the login process if the user requests it.
How about:
[label setNeedsDisplay:YES];
if ([pinText length] == pinLength) [self login];
Yes, that notification exists, in a way. The label will be drawn during the next iteration of the run loop. So do your login at the end of the next run loop iteration, for instance using a performSelector:afterDelay:0 or maybe using
dispatch_async (dispatch_get_main_queue (), ^{ [self login]; });
But a) this depends on the order of execution of rendering versus timers and dispatch_queues. If rendering happens before timer execution, you're all set.
And b) don't block the main thread. Try to perform the login in a background thread/concurrent queue, or do it asynchronously on the main thread if you're using, e.g., NSURLConnection.