Body
The headline is a bit scary, but drawRect
the evaluation is not too much. In the development of weekdays, the random coverage drawRect
method, a little careless will make your program memory burst. Let's look at an example below.
Do an artboard function, but suffer from memory problems have not been solved. The artboard function is simple, which is to record the track of your finger touch and then draw it on the screen. Let's take a look at the following:
We see the state of the left memory deteriorating with the drawing of our fingers. Another careful friend can observe, click on the Blue Rectangle button in the picture, the artboard will pop up, and then no finger draw, the memory will be changed to the MB, and then whenever the finger drawing begins, the memory immediately increases to about three MB stable. For normal IOS apps, such a large memory consumption is intolerable.
Let's analyze Why:
There are two possible reasons why a large number of point objects created during finger drawing are not released in time or other resources are not released in a timely manner.
The second is that the system starts to consume a lot of memory during the drawing process.
The first reason is that a large number of point objects created during finger drawing are not released in time or other resources are not released in time. We temporarily exclude this to save time, because this artboard function works is ARC
written, and we have done code checking and using Instruments
tools to detect memory usage, there is no problem that the so-called object is not released in time.
For the second reason, the system starts to consume a lot of memory during the drawing process. First we noticed a weird and unusual thing is that when the yellow palette just popped up, the memory burst from 18MB to 114MB instantly. The first reason is not the problem, because at this point the finger has not yet made any drawing, that is, there is no point and line of the object, then how can memory explosion?
At this point we have to consider how this artboard function is implemented, the artboard is divided into two steps, the first step to record the user's finger trajectory, this step will generate a large number of points of the object (has been excluded suspects). The second step is to draw to the view or layer, we usually use the frequent drawing method is basically quarz2d's that set of C language framework, and where is the drawing code location? Our main character is finally on the pitch- drawRect
.
Let's take a look at the code of the artboard feature drawing:
- (void)drawRect:(CGRect)rect{ if (!self.paths.count) return; CGContextRef ctx = UIGraphicsGetCurrentContext(); for (BHBPaintPath *path in self.paths) { CGContextSaveGState(ctx); [[UIColor blackColor] set]; [path stroke]; // 关键的一步绘制 CGContextRestoreGState(ctx); }}
To get rid of the plot context stack and the rest of the code that determines the bounds, we just view
draw a black line on the current n
one. How does it seem that the ordinary way of drawing can lead to a spike in memory? We now say that the culprit is drawRect
not enough evidence. We recall the memory when the artboard just popped up, and then we commented out drawRect
all the code. Run the following:
The effect is immediate, after comment out drawRect
, memory immediately return to normal, we finally caught the consumption of memory of the devil, the problem is in the coverage of the drawRect
method. So if we catch the prisoner, is this the end of the article? Not too, we know the reason for the memory explosion, but we don't have a deep analysis of drawRect
why it's so big on memory, and we don't have a solution for the problem. Please look down.
So now let's analyze drawRect
the real cause of the memory explosion:
Why does rewriting drawRect
cause memory to rise a lot?
To figure this out, we need to go through the principles of the IOS program-shaped display. All views displayed in the IOS system are inherited from the base class and are UIView
UIView
responsible for receiving user interaction. but actually the view content you see, including graphics, is UIView
drawn and rendered by an instance layer property, that is CALayer
.
CALayer
The concept of a class is UIView
very similar, it also has a tree-like hierarchical relationship, and can contain picture text, background color, and so on. It differs from the UIView
biggest in that it cannot respond to user interaction, and it can be said that it does not know the existence of a response chain, and its API does not have the ability to respond, although it provides a "method for whether a point is in the layer range".
In each UIView
instance, there is a default support layer that is UIView
responsible for creating and managing this layer. In fact, this CALayer
layer is really used to display on the screen, UIView
just a layer of its packaging, implemented CALayer
delegate
, provides the specific functions of handling event interaction, as well as the animation of the underlying method of the advanced API.
Can be said to CALayer
be UIView
the internal implementation details.
How does it relate to today's theme drawRect
? Don't worry, since we have been sure CALayer
is the final display on the screen, as long as the clues, can be analyzed clearly. In CALayer
fact, it's just an ordinary class in IOS, and it doesn't render directly to the screen, because what you see on the screen is actually a picture. And why we can see CALayer
the content, because there is CALayer
a property inside contents
. You contents
can pass an object of one type by default, id
but only when you pass CGImage
it, it will be displayed on the screen normally. so eventually our graphic rendering landed on the contents
body .
contents
Also known as a homestay map, in addition to assigning it a value CGImage
, we can directly draw it, the method of drawing is the key to this problem, through the inheritance UIView
and implementation of the -drawRect:
method can be customized to draw. The -drawRect:
method does not have a default implementation, because for a reason UIView
, a homestay map is not required and UIView
does not care about what is drawn. If a UIView
-drawRect:
method is detected, it assigns a homestay map to the view, which is equal to the size of the view contentsScale
(this property is related to the screen resolution, and our artboard program presents a different amount of memory in different emulators as well because of its value).
Then back to our artboard, when the artboard appears on the screen, because the method is overridden -drawRect:
, the -drawRect :
method is automatically called. After generating a homestay map , the code inside the method uses the Core Graphics
line to draw n black lines, and then the content is cached, waiting -setNeedsDisplay
to be updated the next time you call.
The method behind the artboard view -drawRect:
is actually the bottom of the CALayer
picture that has been redrawn and saved in the middle, CALayer
the delegate
property by default implements the CALayerDelegate
protocol, when it needs content information will invoke the method in the protocol to take. When the artboard view is redrawn, because its support layer CALayer
's proxy is the artboard view itself, the support layer asks the artboard view to show it a homestay map, which is now called:
- (void)displayLayer:(CALayer *)layer;
If the artboard view implements this method, you can get layer
to set the contents
homestay map directly, if this method is not implemented, the support layer CALayer
will try to invoke:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
Before this method is called, CALayer
an empty homestay map (size bounds
and contentsScale
decision) and a drawing context for a suitable size are created, Core Graphics
prepared for drawing the homestay map, which is passed in as a ctx
parameter. The empty homestay graph generated in this step is quite huge, it is the key to this memory problem, once you have implemented the method in the CALayerDelegate
protocol -drawLayer:inContext:
or UIView
the -drawRect:
method (in fact, the former packaging method), the layer creates a drawing context, The memory required for this context can be derived from this formula: 图层宽
* 图层高
* 4 字节
, the width of the height of the units are pixels. And our artboard program because to support the same as the ape question bank two finger move effect, we open the size of the artboard:
_myDrawer = [[BHBMyDrawer alloc] initWithFrame: CGRectMake(0, 0, SCREEN_SIZE.width*5, SCREEN_SIZE.height*2)];
The artboard view of our artboard is that the amount of memory in iPhone6s plus
the context of the machine is 1920*2
* *, which is the equivalent of 1080*5
4 字节
79MB
Ram , which needs to be re-erased and redistributed each time the layer is redrawn. It is the real reason why our artboard program memory is exploding.
Finally we find out the reason for the memory explosion, so do we have a reasonable solution?
I think the most reasonable way to deal with the need to draw lines like Artboards is to use proprietary layers directly CAShapeLayer
. Let's see what it is:
CAShapeLayer
is a layer subclass that is drawn by vector graphics instead bitmap
of drawing. Use CGPath
to define the shapes you want to draw, which CAShapeLayer
are rendered automatically. It can be a perfect substitute for our direct use of Core Graphics
drawing layer
, compared with the CAShapeLayer
following advantages:
Rendering is fast. Cashapelayer uses hardware acceleration, and drawing the same graphic is much faster than using the Core graphics.
Efficient use of memory. A cashapelayer doesn't need to create a hosted graphic like a normal calayer, so no matter how big it is, it won't take up too much memory.
is not clipped by the layer boundary.
does not appear pixelated.
So in the end our artboard program uses CAShapeLayer
to achieve the line drawing, the performance is very stable, as follows:
Summarize the Drawing performance optimization principles:
The best way to optimize the performance of graphics is not to draw.
Use proprietary layers instead of drawing requirements.
You have to use the drawing to minimize the view area and minimize the redraw frequency.
Asynchronously draws, guesses the content, draws the picture in advance on other threads, and sets the picture directly in the main thread.
Memory Goblin DrawRect-talking about the memory optimization of drawing function