drawRect 또는 drawRect하지 않기 (drawRect / Core Graphics를 서브 뷰 / 이미지와 왜 사용해야 하는가?)
이 질문의 목적을 명확히하기 위해 : 하위 뷰와 drawRect를 사용하여 복잡한 뷰를 만드는 방법을 알고 있습니다. 나는 언제, 왜 다른 하나를 사용 해야하는지 완전히 이해하려고합니다.
또한 미리 최적화하는 것이 합리적이지 않으며 프로파일 링을 수행하기 전에 더 어려운 방법을 수행한다는 것이 이해됩니다. 나는 두 가지 방법 모두에 익숙하며 이제는 더 깊이 이해하고 싶다고 생각하십시오.
테이블 뷰 스크롤 성능을 실제로 매끄럽고 빠르게 만드는 방법을 배우면 많은 혼란이 생깁니다. 물론이 방법의 원래 출처는 트위터 트위터 의 저자 (이전의 tweetie)에서 얻은 것입니다. 기본적으로 테이블 스크롤을 부드럽게 만들려면 비밀은 하위보기를 사용하지 않고 대신 하나의 사용자 정의 uiview에서 모든 도면을 수행하는 것입니다. 본질적으로 많은 서브 뷰를 사용하면 오버 헤드가 많고 부모 뷰에 대해 끊임없이 재구성되어 렌더링 속도가 느려집니다.
공평하게 말하면, 이것은 3GS가 아주 새로운 브랜드 일 때 작성되었으며 iDevices는 그 이후로 훨씬 빨라졌습니다. 또 이 방법은 되어 정기적으로 제안 온 인터 웹 및 다른 곳에서 고성능 테이블. 실제로 이것은 Apple의 Table Sample Code 에서 제안 된 방법이며 여러 WWDC 비디오 ( iOS 개발자를위한 실용 그림 ) 및 많은 iOS 프로그래밍 서적 에서 제안되었습니다 .
그래픽을 디자인하고이를위한 핵심 그래픽 코드를 생성하는 멋진 도구 도 있습니다.
그래서 처음에는 "핵심 그래픽이 존재하는 이유가 있습니다. 그것은 빠릅니다!"라고 믿게됩니다.
그러나 "가능한 경우 선호하는 핵심 그래픽"이라는 아이디어를 얻 자마자 drawRect가 종종 응용 프로그램의 응답 성이 좋지 않고 메모리 비용이 매우 비싸며 실제로 CPU에 부담이 있음을 알기 시작합니다. 기본적으로 " drawRect 재정의 방지 "(WWDC 2012 iOS 앱 성능 : 그래픽 및 애니메이션 )
모든 것처럼 복잡합니다. 어쩌면 drawRect를 사용하는시기와 이유를 이해하도록 도울 수 있습니까?
코어 그래픽을 사용하는 몇 가지 명백한 상황이 있습니다.
- 동적 데이터가 있습니다 (Apple의 주식 차트 예)
- 간단한 크기 조정 가능한 이미지로는 실행할 수없는 유연한 UI 요소가 있습니다
- 한 번 렌더링 된 여러 위치에서 사용되는 동적 그래픽을 작성 중입니다.
핵심 그래픽을 피해야 할 상황이 있습니다.
- 뷰의 속성을 별도로 애니메이션해야합니다
- 뷰 계층 구조가 상대적으로 작으므로 CG를 사용하여 인식 한 추가 노력은 가치가 없습니다.
- 전체를 다시 그리지 않고 뷰 조각을 업데이트하려고합니다.
- 상위 뷰 크기가 변경되면 하위 뷰의 레이아웃을 업데이트해야합니다.
그래서 당신의 지식을 부여하십시오. 어떤 상황에서 drawRect / Core Graphics에 도달합니까 (하위 뷰를 통해 수행 할 수도 있습니까)? 그 결정을 내리는 데 어떤 요인이 있습니까? 버터처럼 부드러운 테이블 셀 스크롤에 권장되는 하나의 사용자 정의보기로 그리는 방법 / 왜, 그러나 Apple은 일반적으로 성능상의 이유로 drawRect를 권장합니까? 간단한 배경 이미지는 무엇입니까 (CG를 사용하여 크기를 조정할 수있는 PNG 이미지를 사용하여 만들 때)?
가치있는 앱을 만들기 위해이 주제에 대한 깊은 이해가 필요하지 않을 수도 있지만 그 이유를 설명 할 수없는 기술 중에서 선택하는 것을 좋아하지 않습니다. 내 뇌가 나에게 화를 낸다.
질문 업데이트
정보를 주셔서 감사합니다. 여기에 몇 가지 명확한 질문이 있습니다.
- 핵심 그래픽으로 무언가를 그리는 중이지만 UIImageViews와 사전 렌더링 된 png로 동일한 것을 달성 할 수 있다면 항상 그 길을 가야합니까?
- 비슷한 질문 : 특히 이와 같은 나쁜 도구를 사용하는 경우 핵심 그래픽에서 인터페이스 요소를 언제 고려해야합니까? (아마도 요소의 표시가 가변적 일 때 (예 : 20 가지 다른 색상 변화가있는 버튼. 다른 경우는?)
- 아래 답변에 대한 이해를 감안할 때 복잡한 UIView 렌더 자체 후 셀의 스냅 샷 비트 맵을 효과적으로 캡처하고 복잡한보기를 스크롤하고 숨기는 동안 표시하면 테이블 셀에 대해 동일한 성능 향상을 얻을 수 있습니까? 분명히 일부 조각을 해결해야 할 것입니다. 내가 가진 흥미로운 생각.
가능하면 UIKit과 하위 뷰를 고수하십시오. 생산성을 높이고 유지 관리가 쉬워야하는 모든 OO 메커니즘을 활용할 수 있습니다. UIKit에서 필요한 성능을 얻지 못하거나 UIKit에서 그리기 효과를 함께 해킹하려는 것이 더 복잡 할 경우 코어 그래픽을 사용하십시오.
일반적인 워크 플로는 하위 뷰로 테이블 뷰를 작성하는 것입니다. 인스트루먼트를 사용하여 앱이 지원할 가장 오래된 하드웨어의 프레임 속도를 측정하십시오. 60fps를 얻을 수 없으면 CoreGraphics로 드롭 다운하십시오. 이 작업을 잠시 수행하면 UIKit이 시간 낭비 일 수 있습니다.
그렇다면 왜 핵심 그래픽이 빠른가요?
CoreGraphics는 정말 빠르지 않습니다. 항상 사용하는 경우 속도가 느려질 수 있습니다. GPU에 오프로드되는 많은 UIKit 작업과는 달리 CPU에서 작업을 수행 해야하는 풍부한 드로잉 API입니다. 화면을 가로 질러 움직이는 공에 애니메이션을 적용해야한다면 초당 60 회 뷰에서 setNeedsDisplay를 호출하는 것이 끔찍한 생각입니다. 따라서 개별 애니메이션이 필요한 뷰의 하위 구성 요소가있는 경우 각 구성 요소는 별도의 레이어 여야합니다.
또 다른 문제는 drawRect를 사용하여 사용자 정의 드로잉을 수행하지 않으면 UIKit이 스톡 뷰를 최적화 할 수 있으므로 drawRect가 작동하지 않거나 합성과 함께 바로 가기가 필요할 수 있다는 것입니다. drawRect를 재정의 할 때 UIKit은 수행중인 작업을 모르기 때문에 느린 경로를 따라야합니다.
이 두 가지 문제는 테이블 뷰 셀의 경우 이점보다 중요합니다. 화면에 뷰가 처음 나타날 때 drawRect가 호출 된 후 내용이 캐시되며 스크롤은 GPU에 의해 수행되는 간단한 변환입니다. 복잡한 계층 구조가 아닌 단일 뷰를 다루기 때문에 UIKit의 drawRect 최적화는 덜 중요합니다. 병목 현상은 코어 그래픽 드로잉을 얼마나 최적화 할 수 있는지가됩니다.
가능하면 UIKit을 사용하십시오. 작동하는 가장 간단한 구현을 수행하십시오. 프로필. 인센티브가 있으면 최적화하십시오.
차이점은 UIView와 CALayer는 기본적으로 고정 이미지를 처리한다는 것입니다. 이 이미지는 그래픽 카드에 업로드됩니다 (OpenGL을 알고있는 경우 이미지를 텍스처로, UIView / CALayer를 이러한 텍스처를 나타내는 다각형으로 생각하십시오). 이미지가 GPU에 있으면 다른 이미지 위에 다양한 수준의 알파 투명도를 적용하더라도 매우 신속하고 심지어 여러 번, 그리고 약간의 성능 저하로도 그릴 수 있습니다.
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 CALayers 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/CALayers). 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 button border etc., as part of the background. But that's usually an optimization that's not needed unless the Instruments app tells you differently.
I'm going to try and keep a summary of what I'm extrapolating from other's answers here, and ask clarifying questions in an update to the original question. But I encourage others to keep answers coming and vote up those who have provided good information.
General Approach
It's quite clear that the general approach, as Ben Sandofsky mentioned in his answer, should be "Whenever you can, use UIKit. Do the simplest implementation that works. Profile. When there's an incentive, optimize."
The Why
- There are two main possible bottlenecks in an iDevice, the CPU and GPU
- CPU is responsible for the initial drawing/rendering of a view
- GPU is responsible for a majority of animation (Core Animation), layer effects, compositing, etc.
- UIView has a lot of optimizations, caching, etc, built in for handling complex view hierarchies
- When overriding drawRect you miss out on a lot of the benefits UIView's provide, and it's generally slower than letting UIView handle the rendering.
Drawing cells contents in one flat UIView can greatly improve your FPS on scrolling tables.
Like I said above, CPU and GPU are two possible bottlenecks. Since they generally handle different things, you have to pay attention to which bottleneck you are running up against. In the case of scrolling tables, it's not that Core Graphics is drawing faster, and that's why it can greatly improve your FPS.
In fact, Core Graphics may very well be slower than a nested UIView hierarchy for the initial render. However, it seems the typical reason for choppy scrolling is you are bottlenecking the GPU, so you need to address that.
Why overriding drawRect (using core graphics) can help table scrolling:
From what I understand, the GPU is not responsible for the initial rendering of the views, but is instead handed textures, or bitmaps, sometimes with some layer properties, after they have been rendered. It is then responsible for compositing the bitmaps, rendering all those layer affects, and the majority of animation (Core Animation).
In the case of table view cells, the GPU can be bottlenecked with complex view hierarchies, because instead of animating one bitmap, it is animating the parent view, and doing subview layout calculations, rendering layer effects, and compositing all the subviews. So instead of animating one bitmap, it is responsible for the relationship of bunch of bitmaps, and how they interact, for the same pixel area.
So in summary, the reason drawing your cell in one view with core graphics can speed up your table scrolling is NOT because it's drawing faster, but because it is reducing the load on the GPU, which is the bottleneck giving you trouble in that particular scenario.
I am a game developer, and I was asking the same questions when my friend told me that my UIImageView based view hierarchy was going to slow down my game and make it terrible. I then proceeded to research everything I could find about whether to use UIViews, CoreGraphics, OpenGL or something 3rd party like Cocos2D. The consistent answer I got from friends, teachers, and Apple engineers at WWDC was that there won't be much of a difference in the end because at some level they are all doing the same thing. Higher-level options like UIViews rely on the lower level options like CoreGraphics and OpenGL, just they are wrapped in code to make it easier for you to use.
Don't use CoreGraphics if you are just going to end up re-writing the UIView. However, you can gain some speed from using CoreGraphics, as long as you do all your drawing in one view, but is it really worth it? The answer I have found is usually no. When I first started my game, I was working with the iPhone 3G. As my game grew in complexity, I began to see some lag, but with the newer devices it was completely unnoticeable. Now I have plenty of action going on, and the only lag seems to be a drop in 1-3 fps when playing in the most complex level on an iPhone 4.
Still I decided to use Instruments to find the functions that were taking up the most time. I found that the problems were not related to my use of UIViews. Instead, it was repeatedly calling CGRectMake for certain collision sensing calculations and loading image and audio files separately for certain classes that use the same images, rather than having them draw from one central storage class.
So in the end, you might be able to achieve a slight gain from using CoreGraphics, but usually it will not be worth it or may not have any effect at all. The only time I use CoreGraphics is when drawing geometric shapes rather than text and images.
'Programming' 카테고리의 다른 글
| Android Activites의 onPause ()와 onStop () ()의 차이점은 무엇입니까? (0) | 2020.06.22 |
|---|---|
| 이 서비스를 사용할 권한이 없습니다 iTunes 앱 업로드 오류 (0) | 2020.06.22 |
| flexbox 항목이 열 모드로 줄 바꿈 될 때 컨테이너의 너비가 커지지 않습니다 (0) | 2020.06.22 |
| 명시 적으로 파일을 닫는 것이 중요합니까? (0) | 2020.06.22 |
| 스트림 객체에 대해 Close () 또는 Dispose ()를 호출해야합니까? (0) | 2020.06.22 |