Use two nested dispatch_async in parallel? - objective-c

Could someone explain to me why I get second NSLog(images count) BEFORE first NSLog(text count) in debug console?
My purpose of having two parallel dispatch_async (each one is nested) is to separately download text and image into a table view using core data. Note that image data are much bigger than text.
I firstly try downloading both text and images in one nesteddispatch_async (jump back to main queue to do mapping and commit of both text and images of course). However, I notice significant delay in displaying cells on screen. I figure there must be some better ways to do it. Any suggestion is appreciate.
-(void)doSometing
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
// download text data
[document.managedObjectContext performBlock:^{
NSLog(#"Text count is %d \n\n",[text count]);
// insert text data to context
// commit text data
}];
});
// see if I can shorten wait time by download image separately
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// download image
[document.managedObjectContext performBlock:^{
NSLog(#"Images count is %d \n\n",[images count]);
// insert images into context
// commit images
}];
});
}

Related

Synchronize operations inside a for loop

I'm using the QBImagePickerControllerlibrary to select more than one image in the provided Image Picker view.
I want to let the user upload more than a single image to the "main" server, with a UIImageView displaying what is uploaded at the moment and a UIProgressView.
Since
- (void)imagePickerController:(QBImagePickerController *)imagePickerController didSelectAssets:(NSArray *)assets
give me an array of ALAssets containig all the images the user selected, I iterate through it this way:
NSMutableArray *IDs = [[NSMutableArray alloc] init];
if([assets count] == 1) {
// single image, no prob.
} else {
for(ALAsset *a in assets) {
//set the image to the one which is uploaded right now
//upload it through a library and tell about status with NSProgress
//write the result into IDs
}
}
My problem is that if the user select two images, the operations appears to be running at the same time, so the UIProgressView is uploaded in what seems a "first come, first served" scenario, while the UIImageViewshows only the last image selected and IDs seems empty.
Is this behaviour due to the method I use to iterate through the array?
What can I do to make each operation mutually exclusive?

How to implement smooth GCD I/O

Having a rough go here with scheduling disk reads via GCD.
Below is a code snippet to load frames from a file that contains about frameCount=1000 frames. In my initial implementation, I did this from the main thread:
[self readFramesFromFrame:0 toFrame:frameCount];
And here's my method:
-(BOOL)readFramesFromFrame:(NSInteger)startFrame toFrame:(NSInteger)endFrame
{
if (frameCount<=0)
return YES;
__block BOOL endRead;
dispatch_async(diskQueue, ^{
do {
dispatch_async(frameQueue, ^{
for (NSInteger i=startFrame; i<endFrame; i++)
[self readFileFrame:i];
});
// ** BEGIN get next batch
NSInteger newStart = endFrame;
NSInteger newEnd = ((endFrame+highWater) < frameCount) ? endFrame+highWater : frameCount;
if (newStart==frameCount)
endRead=YES;
else
endRead=[self readFramesFromFrame:(NSInteger)newStart toFrame:(NSInteger)newEnd];
// ** END get next batch
} while (!endRead);
});
return YES;
}
However, I don't want to load up the initial run with 1000 frames as it takes too long.
I initially want to only load 20 frames (my highwater amount), so I rejigged the code and made an revised call of:
[self readFramesFromFrame:0 toFrame:(frameCount<highWater) ? frameCount : highWater];
But this still takes too long before I get access to the first frame for processing. I am trying to schedule separate blocks of work rather than one large block of work, but I realize I am still effectively scheduling all frames. No improvement.
Two points of explanation. Firstly, my call to [self readFileFrame:frameNumber] does a dispatch_io_read using a readQueue, that is throttled elsewhere by my downstream processing handlers by calling either dispatch_suspend(readQueue) or dispatch_resume(readQueue). I use a low-water value of 10 frames and a high-water value of 20 frames which suspend/resume the readQueue as appropriate. That is working swimmingly well, but is currently predicated upon a reasonably stuff queue of frames.
Secondly, my call to readFileFrame will produce a valid frame of data via the readQueue thread that is accessed (and displayed) via a separate GCD timer.
I have tried dispatching the code snippet between the comments on "next batch" back on the main queue, but that is a disaster also. I thought that adding an additional wrapping serial private queue frameQueue would help, but no.
If I pretend that frameCount is, say, 50 frames, things work very quickly and swimmingly well -- but I only get 50 frames and no more.
How do I rejig this code snippet so that it lazily reads in batches of frames?

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];

Cache thousands of images

I've been developing a music player recently, I'm writing my own pickers.
I'm trying to test my code to it's limits, so I have around 1600 albums in my iPhone.
I'm using AQGridView for albums view, and since MPMediaItemArtwork is a subclass of NSObject, you need to fire up a method on it to get an image from it, and that method scales images.
Scaling for each cell uses too much CPU as you can guess, so my grid album view is laggy, despite all my effort manually driving each cell's includes.
So I thought of start scaling with GCD on app launch, then save them to file, and read that file for each cell.
But, my code
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
MPMediaQuery *allAlbumsQuery = [MPMediaQuery albumsQuery];
NSArray *albumsArray = allAlbumsQuery.collections;
for (MPMediaItemCollection *collection in albumsArray) {
#autoreleasepool {
MPMediaItem *currentItem = [collection representativeItem];
MPMediaItemArtwork *artwork = [currentItem valueForProperty:MPMediaItemPropertyArtwork];
UIImage *artworkImage = [artwork imageWithSize:CGSizeMake(90, 90)];
if (artworkImage) [toBeCached addObject:artworkImage];
else [toBeCached addObject:blackImage];
NSLog(#"%#", [currentItem valueForProperty:MPMediaItemPropertyAlbumTitle]);
artworkImage = nil;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:albumsArray] forKey:#"covers"];
});
NSLog(#"finished saving, sir");
});
in AppDelegate's application:didFinishLaunchingWithOptions: method makes my app crash, without any console log etc.
This seems to be a memory problem, so many images are kept in NSArray which is stored on RAM until saving that iOS force closes my app.
Do you have any suggestions on what to do?
Cheers
Take a look at the recently-released SYCache, which combines NSCache and on-disk caching. It's probably a bad idea to get to a memory-warning state as soon as you launch the app, but that's better than force closing.
As far as the commenter above suggested, mapped data is a technique (using mmap or its equivalent) to load data from disk as if it's all in memory at once, which could help with UIImage loading later on down the road. The inverse (with NSMutableData) is also true, that a file is able to be written to as if it's directly in RAM. As a technique, it could be useful.