UIDocument's saveToURL:forSaveOperation:completionHandler: is suppose to be asynchronous (in documentation, it says it performs this action on a background queue), but my code has a 7-10 seconds lag when calling this.
Here's the code:
NSLog(#"saving image...");
[picDoc saveToURL:[picDoc fileURL]
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
NSLog(#"image save successful!");
UIImage *thumbnail = [image imageByScalingAndCroppingForSize:CGSizeMake(146, 110)];
Picture *picture = [[Picture alloc] initWithThumbnail:thumbnail filename:fileName];
[[set pictures] addObject:picture];
[self setupPictures];
[scrollView scrollRectToVisible:((UIView *)[[scrollView subviews] lastObject]).frame animated:YES];
currentIndex = [[set pictures] count] - 1;
[self showPicture];
} else {
NSLog(#"failed saving image");
}
[SVProgressHUD dismiss];
});
[picDoc closeWithCompletionHandler:nil];
}];
NSLog(#"exit");
And the console:
2012-05-15 07:07:27.417 Picventory[5939:707] saving image...
2012-05-15 07:07:34.120 Picventory[5939:707] exit
2012-05-15 07:07:34.740 Picventory[5939:707] image save successful!
Why is there such a huge lag when the call is asynchronous?
Thanks
Writing the file to disk is asynchronous, but taking the snapshot of the document's data is not. You should use the Time Profiler in Instruments to find out what is actually taking so long. I would guess that you might need to optimize your contentsForType:error: (or equivalent) method, but measure first!
Related
I am having an issue with an iPad app I am developing. I have the following code:
CFTimeInterval startTime = CFAbsoluteTimeGetCurrent();
[self showSpinnerWithMessage:#"Loading settings..."]; // Shows a UIActivityIndicator on screen
dispatch_async(dispatch_get_global_queue(0, 0), ^
{
//Updating items on screen here
[label1 setText:#"Test1"];
[label2 setText:#"Test3"];
//...
CFTimeInterval difference = CFAbsoluteTimeGetCurrent() - startTime;
if (difference < MINIMUM_INDICATOR_SECONDS)
{
[NSThread sleepForTimeInterval:MINIMUM_INDICATOR_SECONDS - difference];
}
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[self removeSpinner]; // Removes UIActivityIndicator from screen
});
};
The problem is that sometimes the UI takes a while to update, and in these particular instances the spinner (UIActivityIndicator) goes away before the UI has actually updated. I realize this is because the items on screen do not actually update until the next run loop so my question is, how can make my [self removeSpinner] call wait until the UI has updated?
Try putting the UI-updating code in the block with removeSpinner.
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[label1 setText:#"Test1"];
[label2 setText:#"Test3"];
[self removeSpinner]; // Removes UIActivityIndicator from screen
});
Put a [self.view setNeedsDisplay]; just before the [self removeSpinner].
I am writing a multithreaded application which needs to upload photos from the ALAssetsLibrary en masse in the background. So I have an NSOperation subclass which finds the appropriate ALAsset via the asset's URL and adds the image to an upload queue.
In the upload queue for the current ALAsset, I need to get the metadata from the image, but I've encountered a problem: both the -metadata and the -fullResolutionImage methods never return when they are called on the ALAssetRepresentation of the ALAsset. They simply hang there indefinitely. I tried printing the value of each of these methods in LLDB, but it hung the debugger up, and I ended up killing Xcode, signal 9 style. These methods are being called on a background queue.
I am testing these on an iPad 2. This is the method in which the ALAsset is added to the upload queue when it is found in the success block of -assetForURL:resultBlock:failureBlock:
- (void)addMediaToUploadQueue:(ALAsset *)media {
#autoreleasepool {
ALAssetRepresentation *defaultRepresentation = [media defaultRepresentation];
CGImageRef fullResolutionImage = [defaultRepresentation fullResolutionImage];
// Return if the user is trying to upload an image which has already been uploaded
CGFloat scale = [defaultRepresentation scale];
UIImageOrientation orientation = [defaultRepresentation orientation];
UIImage *i = [UIImage imageWithCGImage:fullResolutionImage scale:scale orientation:orientation];
if (![self isImageUnique:i]) return;
NSDictionary *imageDictionary = [self dictionaryForAsset:media withImage:i];
dispatch_async(self.background_queue, ^{
NSManagedObjectContext *ctx = [APPDELEGATE createManagedObjectContextForThread];
[ctx setUndoManager:nil];
[ctx performBlock:^{
ImageEntity *newImage = [NSEntityDescription insertNewObjectForEntityForName:#"ImageEntity"
inManagedObjectContext:ctx];
[newImage updateWithDictionary:imageDictionary
inManagedObjectContext:ctx];
[ctx save:nil];
[APPDELEGATE saveContext];
dispatch_async(dispatch_get_main_queue(), ^{
[self.fetchedResultsController performFetch:nil];
});
if (!currentlyUploading) {
currentlyUploading = YES;
[self uploadImage:newImage];
}
}];
});
}
}
I had a similar problem and I was tearing my hair out trying to figure it out.
Turns out while I had thought I setup a singleton for ALAssetsLibrary, my code was not calling it properly and some ALAssets were returning an empty 'fullResolutionImage'
In all of my NSLogs I must have missed the most important message from Xcode:
"invalid attempt to access ALAssetPrivate past the lifetime of its owning ALAssetsLibrary"
Follow this link
http://www.daveoncode.com/2011/10/15/solve-xcode-error-invalid-attempt-to-access-alassetprivate-past-the-lifetime-of-its-owning-alassetslibrary/
I hope that helps
I'm using SDWebImagePrefetcher to prefetch a list of images off my plist (about 100, max size 600kb) in order to save them (on disk possibily) for later use.
Point is that on simulator this is working fine, while on device, it's loading and caching only some of them and I honestly don't get why.
I know this because after prefetching them, i turn off WiFi connection and see if the images have been loaded correctly in the imageView (on detailView), and surprisingly there are only 1\5 of the images prefetched, even if the consoloe said it prefetched them all.
I tried to put in SDWebImagePrefetcher.m in method startPrefetchingAtIndex: this line of code
- (void)startPrefetchingAtIndex:(NSUInteger)index withManager:(SDWebImageManager *)imageManager
{
if (index >= [self.prefetchURLs count]) return;
_requestedCount++;
[imageManager downloadWithURL:[self.prefetchURLs objectAtIndex:index] delegate:self options:self.options];
[cache storeImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[self.prefetchURLs objectAtIndex:index]]]] forKey:[self.prefetchURLs objectAtIndex:index] toDisk:YES];
}
and then in my imageView class:
SDImageCache* cache = [SDImageCache sharedImageCache];
UIImage* cachedImage = [cache imageFromKey:aString fromDisk:YES];
if (cachedImage) {
[imageView setImage:cachedImage];
}
else{
[imageView setImageWithURL:[NSURL URLWithString:anUrl]
placeholderImage:nil options:SDWebImageProgressiveDownload
success:^(UIImage *image) { [activityIndicator stopAnimating];[activityIndicator removeFromSuperview]; }
failure:^(NSError *error) { [activityIndicator stopAnimating];[activityIndicator removeFromSuperview]; }];
[imageView addSubview:activityIndicator];
}
With no concrete results. Always working on simulator, not on device. Am I missing something here?
Should I put some caching method in SDWebImagePrefetcher to store them on disk or what?
I've being trying to make it work but failing every time.
Seems like i found the issue.
I shouldn't set the option to cache ( [prefetcher setOptions:SDWebImageMemoryCacheOnly]; ) ,but I had to leave it in low priority, so now everything works perfectly!
I have a method that constantly loads new image data from the web. Whenever all data for an image has arrived, it is forwarded to a delegate like this:
NSImage *img = [[NSImage alloc] initWithData:dataDownloadedFromWeb];
if([[self delegate] respondsToSelector:#selector(imageArrived:)]) {
[[self delegate] imageArrived:img];
}
[img release];
In imageArrived: the image data is assigned to an NSImageView:
- (void)imageArrived:(NSImage *)img
{
[imageView1 setImage:img];
}
This works nicely, the image is being displayed and updated with every new download cycle. I have profiled my application with Instruments to ensure that there is no leaking - it doesn't show any leaking. However, if I check with Instruments' Activity Monitor, I can see my application grabbing more and more memory with time, roughly increasing by the size of the downloaded images. If I omit [imageView1 setImage:img], memory usage stays constant. My question is: how is that happening? Is my code leaking? How does the NSImage instance within NSImageView determine when to release its data? Thanks for your answers!
When you do initWithData, the retain count on the data is incremented by one. Releasing the image does not change the retain count on the data. That's where your memory is going. The image structures are coming and going the way you want, but the data chunks are accumulating.
save the data handle separately and clean that out after disposing of the nsimage, and all should be well:
(id) olddata = 0; // global or something with persistence
your_routine
{
(id) newdata = dataDownloadedFromWeb;
NSImage *img = [[NSImage alloc] initWithData: newdata];
if([[self delegate] respondsToSelector:#selector(imageArrived:)])
{
[[self delegate] imageArrived:img];
}
[img release];
if (olddata) [olddata release];
olddata = newdata;
}
cleanup
{
if (olddata) [olddata release];
olddata = 0;
}
How do I view the progress for converting a Movie with the following QTKit code?
NSDictionary *dict = [NSDictionary
dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], QTMovieExport,
[NSNumber numberWithLong:kQTFileType3GPP],
QTMovieExportType, nil];
[[movieView movie] writeToFile:#"/tmp/sample.3gp"
withAttributes:dict];
i.e. I want to view the progress of the movie conversion such that I can display it in a progress bar.
Taken from this site: http://www.mactech.com/articles/mactech/Vol.21/21.08/Threads/index.html
If the movie is very large, this method could take quite a while to complete. During that time, the user would be unable to do anything with the application except move windows around. Not very exciting.
A slightly better solution involves using the movie:shouldContinueOperation:withPhase:atPercent:withAttributes: delegate method. This is a wrapper around QuickTime's movie progress function, which will be used to display a dialog box showing the progress of the export and to allow the user to cancel the operation.
Here try this
- (BOOL)movie:(QTMovie *)movie
shouldContinueOperation:(NSString *)op
withPhase:(QTMovieOperationPhase)phase
atPercent:(NSNumber *)percent
withAttributes:(NSDictionary *)attributes
{
OSErr err = noErr;
NSEvent *event;
double percentDone = [percent doubleValue] * 100.0;
switch (phase) {
case QTMovieOperationBeginPhase:
// set up the progress panel
[progressText setStringValue:op];
[progressBar setDoubleValue:0];
// show the progress sheet
[NSApp beginSheet:progressPanel
modalForWindow:[movieView window] modalDelegate:nil
didEndSelector:nil contextInfo:nil];
break;
case QTMovieOperationUpdatePercentPhase:
// update the percent done
[progressBar setDoubleValue:percentDone];
[progressBar display];
break;
case QTMovieOperationEndPhase:
[NSApp endSheet:progressPanel];
[progressPanel close];
break;
}
// cancel (if requested)
event = [progressPanel
nextEventMatchingMask:NSLeftMouseUpMask
untilDate:[NSDate distantPast]
inMode:NSDefaultRunLoopMode dequeue:YES];
if (event && NSPointInRect([event locationInWindow],
[cancelButton frame])) {
[cancelButton performClick:self];
err = userCanceledErr;
}
return (err == noErr);
}
Hope this helps.
If you need any help do let me know.
let me know if this helped a lil.
PK