Make UIImage From UIView but NOT in the main thread - multithreading

I'm using the well-known pattern to create an UIImage from an UIView:
+ (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, [[UIScreen mainScreen] scale]);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
Now, my problem is that i have a very complex view with a lot of subviews on it, so this process of conversion takes about 3+(!!!) seconds.
I tried forking it into another thread that run in the background and it really did improve the performance.
The only problem is that as can I remember, it is not allowed to make UI related stuff not in the main thread.
Am I wrong and this is perfectly fine?
Or - if i'm right - what can be done to improve performance? is there any other method that i can use in a different thread but does the same work?
Thanks a lot!

In the end i just did it in another thread, and everything is working fine.

Related

UIPageViewController memory leak

It seems that UIPageViewController is holding the initial content view controller forever.
For example:
DataViewController *startingViewController = [self.modelController viewControllerAtIndex:0 storyboard:self.storyboard];
NSArray *viewControllers = #[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];
self.pageViewController.dataSource = self.modelController;
The startingViewController is never released until the pageViewController itself it released.
To reproduce this bug, just create a new project in XCode using the Page-Based Application template. And add 3 lines of code into DataViewController.m
#property NSInteger debugIndex; // file scope
NSLog(#"DataViewController[%d] created", self.debugIndex); // in viewDidLoad
NSLog(#"DataViewController[%d] dealloc", self.debugIndex); // in dealloc
And when you scroll the demo App in vertical orientation, you'll get logs like this:
DataViewController[0] created
DataViewController[1] created
DataViewController[2] created
DataViewController[1] dealloc
DataViewController[3] created
DataViewController[2] dealloc
DataViewController[4] created
DataViewController[3] dealloc
DataViewController[5] created
DataViewController[4] dealloc
DataViewController[6] created
DataViewController[5] dealloc
DataViewController[0] is never deallocated.
Any ideas about this?
Thanks!
Are you using transitionStyle UIPageViewControllerTransitionStyleScroll? I encountered the same or a similar problem which seemed to disappear when using page curl animations instead.
The problem was compounded for me because I was allowing a UISliderBar to set the position in the content. So on change of the UISliderBar, I was calling setViewControllers:direction:animated:completion: which caused more and more view controller references to get "stuck" in my UIPageViewController.
I am also using ARC. I have not found an acceptable way to force the UIPageViewController to let go of the extra view controller references. I will probably either end up using the page curl transition or implementing my own UIPageViewController equivalent using a UIScrollView with paging enabled so I can manage my own view controller cache instead of relying on UIPageViewController's broken view controller management.
I'm not sure you still got the problem, but I had the same problem and I found the solution.
I don't know the reason, but it works.
I'm setting the first viewController right after addSubview, rather than before addChlidViewController.
-(void)settingPageViewController{
if (!self.pageViewController) {
self.pageViewController = [[UIPageViewController alloc]initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.delegate = self;
self.pageViewController.dataSource = self;
[self addChildViewController:self.pageViewController];
[self.pageViewController didMoveToParentViewController:self];
[self.containerView addSubview:self.pageViewController.view];
[self.pageViewController setViewControllers:#[[self viewcontrollerAtIndex:0]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
}
and the first viewController will dealloc in the right time.
also, I found if call
[self.pageViewController setViewControllers:#[[self viewcontrollerAtIndex:0]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished){
NSLog(#"finished : %d",finished);
}];
before addSubView and the completion block will not call.
and I reckon this block is the reason why the first viewController didn't dealloc.
I'll go find out why it didn't callback, and improve the answer~
cheers
After a few attempts to figure out what was happening on a similar issue, I noticed that in my project there were 2 reasons that caused a retain problem and resulted in having a UIPageViewController being forever retained.
1) there was a circular reference between the UIPageViewController and the UIViewcontroller that was presented (this was fixed by changing the properties to weak from strong in both classes)
2) and the main fix consisted in changing
[self setViewControllers:#[initialDetailsViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
to
__weak __typeof__(self) weakSelf = self;
[weakSelf setViewControllers:#[initialDetailsViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
I hope this helps someone
same problem here
i resolved it by keeping my initial viewController in the variable
and instead of creating the same vc on particular pageIndex i just reuse it
I had same problem and solved the following:
[startingViewController release]; where the end point of initialization.
then the first ViewController will be deallocated.

my app is very slow while doing pinch gesture recognizer

I'm doing an iPhone app in iOS 5.
In that I'm expanding and reloading the uitableview while recognizing the pinch gesture.
It works great in simulator. But in device it works very slow. For example in device after the UIGestureRecognizerStateEnded only all the rows will be expanded, but in simulator the rows are expanding and reloading while UIGestureRecognizerStateChanged itself.
any suggestions for memory issues?
my code is here
if (pinchRecognizer.state == UIGestureRecognizerStateBegan) {
self.initialPinchHeight = rowHeight;
[self updateForPinchScale:pinchRecognizer.scale];
}
else {
if (pinchRecognizer.state == UIGestureRecognizerStateChanged) {
[self updateForPinchScale:pinchRecognizer.scale];
}
else if ((pinchRecognizer.state == UIGestureRecognizerStateCancelled) || (pinchRecognizer.state == UIGestureRecognizerStateEnded)) {
}
}
-(void)updateForPinchScale:(CGFloat)scale{
CGFloat newHeight = round(MAX(self.initialPinchHeight * scale, DEFAULT_ROW_HEIGHT));
rowHeight = round(MIN(30.0, newHeight));
/*
Switch off animations during the row height resize, otherwise there is a lag before the user's action is seen.
*/
BOOL animationsEnabled = [UIView areAnimationsEnabled];
[UIView setAnimationsEnabled:NO];
[self.tableView beginUpdates];
NSArray *visibleRows = [self.tableView indexPathsForVisibleRows];
[self.tableView reloadRowsAtIndexPaths:visibleRows withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
[UIView setAnimationsEnabled:animationsEnabled];
}
Before trying to figure out what to optimize, you should measure where the problem is. You can do this using the Time Profile and Core Animation instruments. Use Xcode's Product menu and select Profile. Make sure you profile while you are attached to the device, which, as you have noticed, has different performance characteristics than the simulator.
The Time Profile instrument will help you identify work done on the CPU. The Core Animation instrument will help you identify work being done by Core Animation both on the CPU and GPU. Pay attention to the Core Animation instrument's Debug Option checkboxes. They're a little cryptic, but will help you visually identify the parts of your UI that are making the Core Animation do a lot of work.
Documentation on the different instruments available for iOS are here.
I also recommend the WWDC video covering Core Animation.

NSOperationQueue and NSFetchedResultsController

i use a combination of queue and resultscontroller to update and display some coredata objects.
in my uitableviewcontroller i call every X second a method in my main controller object.
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(test:) userInfo:nil repeats:YES];
}
- (void)test:(NSTimer*)theTimer {
[[MainController instance] updatePersons];
}
In this method a custom NSOperation object will be added to my main Q.
- (BOOL)updatePersons {
UpdatePersonsOperation* u = [[UpdatePersonsOperation alloc] init];
[u setQueuePriority:NSOperationQueuePriorityVeryHigh];
[operationQ u];
[u release];
The operation itself creates a new managedobjectcontext and tries to download some xml from the web and tries to update the coredata database... (This code works!)
In my main controller i receive the context changed message and i use mergeChangesFromContextDidSaveNotification to merge and update my main object context. All resultscontroller use this main object context.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationManagedObjectContextDidSave:) name:
NSManagedObjectContextDidSaveNotification object:nil];
Actually everything works but when i insert a new object inside a NSOperation it takes 4-6 seconds till the UI updates and displays this new object... Furthermore the UI blocks... Its not possible to scroll or trigger a other interaction...
When i don't use a queue and i put my code to download and update the objects into a method inside my uitableviewcontroller class and i use this
[NSThread detachNewThreadSelector:#selector(codeFromUpdatePersonsOperation) toTarget:self withObject:nil];
Everything works very well without any delay or UI freeze...
Can someone explain me this behavoir?
Thanks
Another problem may have been that updating the UI needs to take place on the Main thread. I was experiencing the same issue that you reported, and eventually figured out that if you call
[target performSelectorOnMainThread:#selector(methodToUpdateUI:) withObject:dataFromRetrievalOperation waitUntilDone:NO];
This will cause the UI to update immediately when the thread has finished processing. Otherwise, it waits about 5 seconds before the UI animations take place.

iOS threading "Modifying layer that is being finalized"

I'm analyzing an image which takes some time and meanwhile I want to display a progress indicator. For this I'm using MBProgressHUD.
It almost works... I get this error: "Modifying layer that is being finalized". I guess it's due to the fact that I do pushViewController not in my main thread. Am I right? Any ideas on how to correct this issue?
My code:
- (IBAction)buttonReadSudoku:(id)sender
{
mbProgress=[[MBProgressHUD alloc] initWithView:self.view];
mbProgress.labelText=#"Läser Sudoku";
[self.view addSubview:mbProgress];
[mbProgress setDelegate:self];
[mbProgress showWhileExecuting:#selector(readSudoku) onTarget:self withObject:nil animated:YES];
}
- (void)readSudoku
{
UIImage *image = imageView.image;
image = [ImageHelpers scaleAndRotateImage:image];
NSMutableArray *numbers = [SudokuHelpers ReadSudokuFromImage:image];
sudokuDetailViewController = [[SudokuDetailViewController alloc] init];
[sudokuDetailViewController setNumbers:numbers];
[[self navigationController] pushViewController:sudokuDetailViewController animated:YES];
}
Define a new method to push your detail view controller and use -performSelectorOnMainThread:withObject:waitUntilDone: to perform it on the main thread. Don't try to make any UI changes from other threads.
All UI changes must be in the main thread, as you note. Rather than you off-main-thread method make any changes to the UI, send an NSNotification to the current viewController telling it to do the UI work.
This is an especially good route if you're crossing an MVC border, or if you already have a viewController that knows what to do so that writing a separate method results in duplicate code.

iOS4 & background [UIImage setImage:]

Up to iOS 3.2, I used this kind of code to load UIImageView image in background, and it worked fine...
Code:
- (void)decodeImageName:(NSString *)name
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *newImage = [UIImage imageNamed:name];
[myImageView setImage:newImage];
[pool release];
}
...
[self performSelectorInBackground:#selector(decodeImageName:) withObject:#"ID"]
... even if [UIImageView setImage:] was not thread-safe !
But since iOS 4, it doesn't work any more... Images appear on screen two seconds after setImage call. And if I do a [myImageView performSelectorOnMainThread:#selector(setImage:) withObject:newImage waitUntilDone:YES] instead of [myImageView setImage:newImage], images appear immediately but seem to be re-decoded again on-the-fly (ignoring the previous [UIImage imageNamed:] which should have already decoded the image data), causing a pause on my main thread... Even if documentation says The underlying image cache is shared among all threads..
Any thought ?
Don’t do it in the background! It’s not thread-safe. Since an UIImageView is also an NSObject, I think that using -[performSelectorOnMainThread:withObject:waitUntilDone:] on it might work, like:
[myImageView performSelectorOnMainThread:#selector(setImage:) withObject:newImage waitUntilDone:NO];
And it’s UIImage which is newly made thread-safe. UIImageView is still not thread-safe.
performSelectorInBackground: runs a selector in a background thread. Yet setImage: is a UI function. UI functions should only be run on the main thread. I do not have insight into the particular problem, but this is the first gut feel about this code, and it may be that iOS4 handles the (non-supported) mechanism of running UI functions in background threads somehow differently.
If you're using iOS 4.0, you should really consider reading up on blocks and GCD. Using those technologies, you can simply replace your method with:
- (void)decodeImageName:(NSString *)name
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *newImage = [UIImage imageNamed:name];
dispatch_async(dispatch_get_main_queue(), ^{
[myImageView setImage:newImage];
}
[pool release];
}
Let's quote:
#property(nonatomic, readonly) CGImageRef CGImage
Discussion
If the image data has been purged because of memory constraints, invoking this method forces that data to be loaded back into memory. Reloading the image data may incur a performance penalty.
So you might be able to just call image.CGImage. I don't think CGImages are lazy.
If that doesn't work, you can force a render with something like
// Possibly only safe in the main thread...
UIGraphicsBeginImageContext((CGSize){1,1});
[image drawInRect:(CGRect){1,1}];
UIGraphicsEndImageContext();
Some people warn about thread-safety. The docs say UIGraphics{Push,Pop,GetCurrent}Context() are main-thread-only but don't mention anything about UIGraphicsBeginImageContext(). If you're worried, use CGBitmapContextCreate and CGContextDrawImage.

Resources