I am using the follow code that is executed on the main thread to take a screenshot of an off screen (not a subview of self.view) view that then is displayed in an UIImageView. Everything works fine in terms of functionality, however because this code is being run on an extension, there are much stricter memory bounds (I've read ~30 MB is the cap?),
UIGraphicsBeginImageContextWithOptions(CGSizeMake(self.screenshotView.frame.size.width, self.screenshotView.frame.size.height-2), YES, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.screenshotView.layer renderInContext:context];
_generatedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.previewImageView.image = _generatedImage;
The method this code resides in gets called using performSelectorOnMainThread whenever a UIButton is pressed. A UIAlertController is also presented to handle the freezing of the UI, but if the button is pressed continuously (after dismissal of the UIAlertController), the first few times the memory usage will stay around the baseline (~15 MB), but then spike to ~30 MB and stay there until the method is called again a few seconds later and then when it is done rendering, it falls back to ~15 MB.
I'm unsure what is causing this behavior, why the memory usage doesn't stick around ~15 MB all the time, and I'm unsure as to what is the reason it spikes like that when the method is called continuously. It sounds like more that one thing is happening at once sometimes? How can I make sure this doesn't happen and only dismiss the UIAlertController when it is safe to render again without spiking memory usage.
The memory spike is not due to the renderInContext: call despite everything including instruments pointing to it, it is however due to the subviews of the captured UIView. In my case it was a faulty constraint that caused a UITextView to set its height to 2000.
To whomever has a problem like this and cannot figure it out, move on from renderInContext: and look at your subviews to make sure they are proper.
1.Could you elaborate more on why are you trying to capture screenshot on main thread? Run it async with:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
});
This might resolve the hang issue & try to execute these line of codes inside autoreleasepool which will help in reducing memory foot print.
2.Have you tried drawViewHierarchyInRect: afterScreenUpdates: It's much faster & could be more efficient and help with your memory issue.
That method renders to the context and then you can just call:
CGImageRef image = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
but it should be executed inside an autoreleasepool. If not then memory keeps on rising.
EDIT
3.If you can prevent multiple context creation with synchronizing capture method.It might help in spiking memory:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t renderQueue = dispatch_queue_create("com.throttling.queue", NULL);
- (void) captureScreen {
if (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW) == 0) {
dispatch_async(renderQueue, ^{
// capture
dispatch_semaphore_signal(semaphore);
});
}
}
Related
This is perhaps more existential than a concrete question, but I'm struggling with a bit of a user experience issue in my app.
In my app, I have a task that converts a UIView to an UIImage, does some off-screen processing, and updates the UI for the user. All of this happens on the Main Queue, as is required for such UIKit tasks.
I want to have an activity indicator in my app (I'm using a custom designed one, but a regular UIActivityIndicator demonstrates the same issue), which I also have running on the Main Queue, prior to the aforementioned task.
My issue is that once the UIView processing kicks in, my activity indicator freezes until the task completes. This is obviously due to the main queue handling another, more intensive task, hereby hanging the spinner. My curiosity is; how can I ensure the spinner continues, regardless of the UI work happening on the main queue?
Thanks!
I'm afraid this is impossible unless you do the heavyweight operation on the background thread.
You can try to give the main thread a little bit air to breathe by chunking the operation to smaller parts, if that can be done. That would at least allow some updates of the spinner.
While I think that you should keep taking an image and updating the UI on the main thread, considering putting processing the image at the background thread, if that is possible.
I agree with Milan. I'd suggest a slightly different flow:
Start your activity indicator spinning.
Grab your view and convert it to an image.
Pass the image processing to a GCD background queue for processing, and pass in a completion handler.
When the background processing is complete, invoke the completion handler on the main thread. In the body of the completion handler, stop/hide the activity indicator.
I would suggest something like this.
-(void)render {
startSpinner();
backgroundRenderQueue = dispatch_queue_create("backgroundQueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(backgroundQueue, ^{
//Render image here
UIImage *image = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageview.image = image;
stopeSpinner()
});
});
}
I'm experiencing a memory leak in my application and it seems to originate from CALayer. It looks as though it only affects old iPads; I see the problem with iPad 1 & 2, but iPad Air is ok. I have a crash report from an iPad 1 showing that my app was 'jettisoned' due to insufficient memory, this leak is my main suspect.
Background
During operation setNeedsDisplay is continuously called every 40ms by the network thread on various UIViews to update their visuals, see function below.
- (void)setNeedsRepaint
{
[self performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
}
Simulating iPad2 and using the allocations instrument, I see that every time setNeedsDisplay is called the malloc 64 reference count permanently rises. The responsible library is libdispatch.dylib and the caller is dispatch_continuation_alloc_from_heap.
The iPad Air simulator doesn't show this issue, in this case the malloc 32 reference count only temporarily rises.
I am seeing the malloc 64 reference count rise even on occasions where setNeedsDisplay originates in the GUI thread and isn't dispatched via performSelectorOnMainThread.
Below is a screen shot of the allocations instrument. The malloc marked 3 is the leak in question. The mallocs marked 1 & 2 leak much slower, but are still a slight concern.
Steps taken
To rule out a memory leak in drawRect I commented out all the code between the braces, but the leaked memory still continues to accumulate.
If I don't override the drawRect method I don't see the leak, but I need to in order to draw and update my view. Nor do I see it if setNeedsDisplay is not called, I can call a dummy function instead via performSelectorOnMainThread with no memory leak.
I've tried using blocks and dispatch_async instead of performSelectorOnMainThread to run setNeedsDisplay on the GUI thread.
I also tried reducing the app down so that setNeedsDisplay is only repeatedly called on a single view. Then removing all pointers to that view so ARC cleans it up in the hope that the stray mallocs might get cleaned up with it.
I've tried setting the CALayer contents directly instead of calling setNeedsDisplay. It renders, but the malloc count rises in exactly the same way.
self.layer.contents = (__bridge id) _dummyCGImageRef;
After reading this I thought the leak maybe due to the queue becoming swamped. However, slowing the rate of my function call by 10 just made the memory leak grow 10x slower.
Conclusions
The leak really seems tied to CALayer rather than the dispatch queue and performSelectorOnMainThread. It looks like this issue is fixed in later iPads, but I still need a workaround for older models.
Questions
Has anyone got any tips for debugging this?
Is another instrument better suited to finding the exact cause?
Is this a peculiarity of the simulator and what I'm seeing isn't the reason my app is jettisoning?
Anyone know what's causing this? Is this an historic bug since it doesn't affect iPad Air?
I there a subclassing trick I could do with CALayer to prevent the backing store allocating memory, or another way I update my view visuals?
Thanks.
I have a very annoying problem. I have a ViewController with an UIImageView in it. The UIImageView should display a slide show. The images for the are coming from NSURL, so it takes a bit of time.
- (void)viewDidLoad {
[super viewDidLoad];
[self loadImages];
}
This is how I get the Images from the NSURL. The problem I have is, that while the Images are loading I only see a black screen.
At the beginning and at the end of the -(void)loadImages method I implemented a UIActivityIndicator to display the loading time.
I already tried -(void)viewWillAppear and -(void)viewDidLayoutSubviews but nothing worked.
Thanks for help
Jannes
What you need to do is load your images asynchronously and populate them as they come in (with delegate or block callbacks, depending on the API you use). You should absolutely never run networking code, or any other potentially long-running operation, synchronously on the main thread. The reason for this is that the UI runs on the main thread, so if you block it with your own operations, the UI cannot update and your app will become unresponsive.
Whether you're using Apple's networking frameworks or something like AFNetworking (highly recommended), there are many ways to do networking asynchronously with minimal work.
You're probably downloading your images synchronously. Try wrapping it in a dispatch_async block to run it on a different thread.
Example:
-(void)loadImages {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
// download the images here
dispatch_async(dispatch_get_main_queue(), ^(void) {
// update the UI on the main thread
});
});
}
Check out the SDWebImage library as well as most of this heavy lifting is done for you.
I really need help.
I am creating simple image processing app, where I load an image from the camera roll or I take a picture. I have a brightness control (slider) that will adjust the brightness of the image. The problem is slider works in real time on the simulator, but on the ipad there is small delay in response. I have tried everything, but don't seem to have any luck.
Please help. I have seen other apps where slider works smoothly without any delay. What am I doing wrong?
Thanks
I'm going to take a guess on some things that were left out of the initial question.
1) To clarify, the problem is: movement of the slider is not smooth.
2) Also, as a result of, or in combination with, this UI roughness, there is a delay in change to the image.
I'm not sure what your implementation looks like, but, it sounds like you're doing too much work and/or too much on the main thread.
So, heres what a functioning implementation might do:
- (void)sliderChanged:(UISlider *)sender
{
[self adjustImageBrightnessWithValue:sender.value;
}
- (void)adjustImageBrightnessWithValue:(CGFloat)value
{
[self cancelCurrentWork]; // Maintain a reference to an operation and cancel it
[self adjustImageBrightnessAsyncWithValue:(CGFloat)value originalImage:self.imageView.image completion:^(UIImage *finalImage)
{
self.imageView.image = finalImage;
}
}
adjustImageBrightnessAsyncWithValue takes a value, original image, and completion block. It creates an operation (via NSOperation or NSOperationQueue, probably both) and keeps track of that operation. The operation applies the algorithm to the original image in the background. Once its done, the completion block sets the final image in your image view, on the main thread.
The only things that happen on the main thread are: get the slider changed callback, canceling previous work, starting new work, and setting the final image. Everything else should happen in the background. Canceling work is an optimization for the case that the user moves the slider too fast for the image to be modified before the value changes again. Once the slider doesn't change long enough to do the modification, the result will be visible. The slider should always be smooth because nothing is blocking the main thread.
EDIT
Using an operation queue...
Declare a member variable:
NSOperationQueue *m_queue;
...
Initialize it in an init method:
m_queue = [NSOperationQueue new];
m_queue.maxConcurrentOperationCount = 1; // So it only does one brightness calculation at a time and there are no concurrency issues.
...
- (void)adjustImageBrightnessWithValue:(CGFloat)value
{
[m_queue cancelAllOperations];
[m_queue addOperationWithBlock:^
{
UIImage *adjustedImage = [mainimage brightness:value]; // Not sure where this method is coming from, but this code assumes it returns a copy of mainimage with the brightness adjusted.
dispatch_async(dispatch_get_main_queue(), ^
{
imageview.image = adjustedImage;
});
}];
}
Also, as an aside, you might take a look at GPUImage, discussed here: http://nshipster.com/gpuimage/ for numerous fast, powerful image modification techniques/APIs.
Use
slider.isContinuous = false
That's it will solve the problem.
I have an iPhone app where based on some parameters an image gets recreated. Since this image recreation can take some time, I use a separate thread to create the image.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0);
// costly drawing
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.image = newImage;
});
});
The parameters affecting the recreation of this image can vary faster than the image can get recreated, so I'd like to "pause" recreation when needed and only perform one such dispatch_async call at a time.
Then as more requests to recreate the image arrive, only remember the last one (with the most up to date parameters), and as soon as the image recreation finished start one for those parameters.
It doesn't matter that all the other calls are never done, the image would be overwritten anyway.
What's the best way to achieve this?
You may want to consider using NSOperationQueue since you can cancel existing queue items every time a new one is added.
Using dispatch_async will run whatever you place in the block until completion (unless you suspend the entire queue) so there's not a great way of stopping prior queue items without setting some sort of a cancellation flag (in which case, they are just short-circuited but the block is still run to completion).
NSOperationQueue is built on top of GCD so it provides the same backgrounding capabilities, it just gives you more control over the queue, which is what you need in this case. It can also be run concurrently on multiple threads, but you shouldn't need that.