7

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.

2 Answers2

3

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

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);
        });
    }
}
Ellen
  • 5,180
  • 1
  • 12
  • 16
  • drawViewHierarchyInRect: won't work if the view I want to screenshot isn't a subview, am I correct? It is not a subview, and if I remember correctly with my testing, it renders a black image. –  Jul 07 '17 at 06:22
  • Also tried running it async, same memory spike, trying now async with autorelease pool. –  Jul 07 '17 at 06:24
  • No luck even with the autorelease pool running async. –  Jul 07 '17 at 06:26
  • yes you should have view reference to use drawViewHierarchyInRect. Have you tried calling this on controller.view ? It will capture your current view on top. – Ellen Jul 07 '17 at 06:32