Why an empty implementation of drawRect: will adve

2019-03-20 11:57发布

I am subclassing my UIView class. The Xcode (I am using 4.6.3) auto generated code says,

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
}
*/

It raised few questions in my mind :

1) Why an empty implementation of drawRect: will cause adverse performance during animation.

2) When should I implement drawRect:.

3) If I am implementing drawRect: then what should be taken as precaution for the best practice.

2条回答
爷、活的狠高调
2楼-- · 2019-03-20 12:20

To know when to use -drawRect: and when to do things like use a UIImageView, I'll have to explain a bit more:

UIView and CGLayer essentially deal in fixed images. These images are uploaded to the graphics card (if you know OpenGL, think of an image as a texture, and a UIView/CGLayer as a polygon showing such a texture). Once an image is on the GPU, it can be drawn very quickly, and even several times, and (with a slight performance penalty) even with varying levels of alpha transparency on top of other images.

CoreGraphics/Quartz is an API for generating images. It takes a pixel buffer (again, think OpenGL texture) and changes individual pixels inside it. This all happens in RAM and on the CPU, and only once Quartz is done, does the image get "flushed" back to the GPU. This round-trip of getting an image from the GPU, changing it, then uploading the whole image (or at least a comparatively large chunk of it) back to the GPU is rather slow. Also, the actual drawing that Quartz does, while really fast for what you are doing, is way slower than what the GPU does.

That's obvious, considering the GPU is mostly moving around unchanged pixels in big chunks. Quartz does random-access of pixels and shares the CPU with networking, audio etc. Also, if you have several elements that you draw using Quartz at the same time, you have to re-draw all of them when one changes, then upload the whole chunk, while if you change one image and then let UIViews or CGLayers paste it onto your other images, you can get away with uploading much smaller amounts of data to the GPU.

When you don't implement -drawRect:, most views can just be optimized away. They don't contain any pixels, so can't draw anything. Other views, like UIImageView, only draw a UIImage (which, again, is essentially a reference to a texture, which has probably already been loaded onto the GPU). So if you draw the same UIImage 5 times using a UIImageView, it is only uploaded to the GPU once, and then drawn to the display in 5 different locations, saving us time and CPU.

When you implement -drawRect:, this causes a new image to be created. You then draw into that on the CPU using Quartz. If you draw a UIImage in your drawRect, it likely downloads the image from the GPU, copies it into the image you're drawing to, and once you're done, uploads this second copy of the image back to the graphics card. So you're using twice the GPU memory on the device.

So the fastest way to draw is usually to keep static content separated from changing content (in separate UIViews/UIView subclasses/CGLayers). Load static content as a UIImage and draw it using a UIImageView and put content generated dynamically at runtime in a drawRect. If you have content that gets drawn repeatedly, but by itself doesn't change (I.e. 3 icons that get shown in the same slot to indicate some status) use UIImageView as well.

One caveat: There is such a thing as having too many UIViews. Particularly transparent areas take a bigger toll on the GPU to draw, because they need to be mixed with other pixels behind them when displayed. This is why you can mark a UIView as "opaque", to indicate to the GPU that it can just obliterate everything behind that image.

If you have content that is generated dynamically at runtime but stays the same for the duration of the application's lifetime (e.g. a label containing the user name) it may actually make sense to just draw the whole thing once using Quartz, with the text, the label's border etc., as part of the background. But that's usually an optimization that's not needed unless the Instruments app tells you differently.

查看更多
Lonely孤独者°
3楼-- · 2019-03-20 12:29

Only override draw() if you perform custom drawing.

This means if we do not perform custom drawing, we should not write the overriding function. The "default" draw() is used. All graphics, if any, are handled "natively" so performance is best optimsed.

An empty implementation adversely affects performance during animation.

If we do override draw(), but we do not write any code in the function leaving this func empty, that is (1) an implementation of override draw() and it is also (2) an empty one as well. This will degrade performance. The following code

public override func (_ r: CGRect) {
    // empty
}

is not efficient during animation. Persumably the system will attempt to redraw the whole canvas rather than redrawing only the affected part?

查看更多
登录 后发表回答