drawRect를 그릴 지 여부를 그릴 때 (drawRect / Core Graphics를 서브 뷰 / 이미지와 언제 사용해야 하는가?)


142

이 질문의 목적을 명확히하기 위해 : 하위 뷰와 drawRect를 사용하여 복잡한 뷰를 만드는 방법을 알고 있습니다. 나는 언제, 왜 다른 하나를 사용 해야하는지 완전히 이해하려고합니다.

또한 미리 최적화하는 것이 합리적이지 않으며 프로파일 링을 수행하기 전에 더 어려운 방법을 수행한다는 것이 이해됩니다. 나는 두 가지 방법 모두에 익숙하며 이제는 더 깊이 이해하고 싶다고 생각하십시오.

테이블 뷰 스크롤 성능을 정말 부드럽고 빠르게 만드는 방법을 배우는 데 많은 혼란이 생깁니다. 물론이 방법의 원래 출처는 트위터 트위터저자 (이전의 tweetie)에서 얻은 것입니다. 기본적으로 테이블 스크롤을 부드럽게 만들려면 비밀은 하위보기를 사용하지 않고 대신 하나의 사용자 정의 uiview에서 모든 도면을 수행하는 것입니다. 본질적으로 많은 서브 뷰를 사용하면 오버 헤드가 많고 부모 뷰에 대해 끊임없이 재구성되어 렌더링 속도가 느려집니다.

공평하게 말하면, 이것은 3GS가 아주 새로운 브랜드 일 때 작성되었으며 iDevices는 그 이후로 훨씬 빨라졌습니다. 또 이 방법은 되어 정기적으로 제안인터 웹 및 다른 곳에서 고성능 테이블. 실제로 이것은 Apple의 Table Sample Code 에서 제안 된 방법이며 여러 WWDC 비디오 ( iOS 개발자를위한 실용 그림 ) 및 많은 iOS 프로그래밍 서적 에서 제안되었습니다 .

그래픽을 디자인하고이를위한 핵심 그래픽 코드를 생성하는 멋진 도구 도 있습니다.

그래서 처음에는 "핵심 그래픽이 존재하는 이유가 있습니다. 빠릅니다!"라고 믿게됩니다.

그러나 "가능한 경우 가장 좋아하는 핵심 그래픽"이라는 아이디어를 얻 자마자 drawRect가 종종 응용 프로그램의 응답 성이 좋지 않고 메모리 비용이 매우 비싸고 실제로 CPU에 부담이 있음을 알기 시작합니다. 기본적으로 " drawRect 재정의 방지 "(WWDC 2012 iOS 앱 성능 : 그래픽 및 애니메이션 )

모든 것처럼 복잡합니다. 어쩌면 drawRect를 사용하는시기와 이유를 이해하도록 도울 수 있습니까?

코어 그래픽을 사용하는 몇 가지 명백한 상황이 있습니다.

  1. 동적 데이터가 있습니다 (Apple의 주식 차트 예)
  2. 간단한 크기 조정 가능한 이미지로는 실행할 수없는 유연한 UI 요소가 있습니다
  3. 한 번 렌더링 된 여러 위치에서 사용되는 동적 그래픽을 작성 중입니다.

핵심 그래픽을 피해야 할 상황이 있습니다.

  1. 뷰의 속성을 별도로 애니메이션해야합니다
  2. 뷰 계층 구조가 상대적으로 작으므로 CG를 사용한 추가 노력은 가치가 없습니다.
  3. 전체를 다시 그리지 않고 뷰 조각을 업데이트하려고합니다.
  4. 상위 뷰 크기가 변경되면 하위 뷰의 레이아웃을 업데이트해야합니다.

그래서 당신의 지식을 부여하십시오. 어떤 상황에서 drawRect / Core Graphics에 도달합니까 (하위 뷰로도 수행 할 수 있습니까)? 그 결정을 내리는 데 어떤 요인이 있습니까? 버터처럼 부드러운 테이블 셀 스크롤에 권장되는 하나의 사용자 정의보기로 그리기 / 어떻게 / 왜 Apple은 일반적인 성능상의 이유로 drawRect를 권장합니까? 간단한 배경 이미지는 무엇입니까 (CG로 크기를 조정하거나 크기를 조정할 수있는 PNG 이미지를 사용하여 만들 때)?

가치있는 앱을 만들기 위해이 주제에 대한 깊은 이해가 필요하지는 않지만 그 이유를 설명 할 수없는 기술 중에서 선택하는 것을 좋아하지 않습니다. 내 뇌가 나에게 화를 낸다.

질문 업데이트

정보를 주셔서 감사합니다. 여기에 몇 가지 명확한 질문이 있습니다.

  1. 핵심 그래픽으로 무언가를 그리는 중이지만 UIImageViews와 사전 렌더링 된 png로 동일한 것을 달성 할 수 있다면 항상 그 길을 가야합니까?
  2. 비슷한 질문 : 특히 이와 같은 나쁜 도구를 사용하는 경우 핵심 그래픽에서 인터페이스 요소를 언제 고려해야합니까? (아마도 요소의 표시가 가변적 일 때 (예 : 20 가지 다른 색상 변형이있는 버튼. 다른 경우는?)
  3. 아래 답변에 대한 이해를 바탕으로 복잡한 UIView 렌더 자체 후 셀의 스냅 샷 비트 맵을 효과적으로 캡처하고 복잡한보기를 스크롤하고 숨기는 동안 표시하면 테이블 셀의 동일한 성능 향상을 얻을 수 있습니까? 분명히 일부 조각을 해결해야 할 것입니다. 내가 가진 흥미로운 생각.

답변:


72

가능하면 UIKit과 하위 뷰를 고수하십시오. 생산성을 높이고 유지 관리가 쉬워야하는 모든 OO 메커니즘을 활용할 수 있습니다. UIKit에서 필요한 성능을 얻지 못하거나 UIKit에서 그리기 효과를 함께 해킹하려는 것이 더 복잡 할 경우 코어 그래픽을 사용하십시오.

일반적인 워크 플로는 하위 뷰로 테이블 뷰를 작성하는 것입니다. 인스트루먼트를 사용하여 앱이 지원할 가장 오래된 하드웨어의 프레임 속도를 측정하십시오. 60fps를 얻을 수 없으면 CoreGraphics로 드롭 다운하십시오. 이 작업을 잠시 수행하면 UIKit이 시간 낭비 일 수 있습니다.

그렇다면 왜 핵심 그래픽이 빠른가요?

CoreGraphics는 정말 빠르지 않습니다. 항상 사용하는 경우 속도가 느려질 수 있습니다. GPU에 오프로드되는 많은 UIKit 작업과는 달리 CPU에서 작업을 수행 해야하는 풍부한 드로잉 API입니다. 화면을 가로 질러 움직이는 공에 애니메이션을 적용해야한다면 초당 60 회 뷰에서 setNeedsDisplay를 호출하는 것이 끔찍한 생각입니다. 따라서 개별 애니메이션이 필요한 뷰의 하위 구성 요소가있는 경우 각 구성 요소는 별도의 레이어 여야합니다.

또 다른 문제는 drawRect를 사용하여 사용자 정의 드로잉을 수행하지 않으면 UIKit이 스톡 뷰를 최적화 할 수 있으므로 drawRect가 작동하지 않거나 합성과 함께 바로 가기가 필요할 수 있다는 것입니다. drawRect를 재정의 할 때 UIKit은 수행중인 작업을 모르기 때문에 느린 경로를 따라야합니다.

이 두 가지 문제는 테이블 뷰 셀의 경우 이점보다 중요합니다. 화면에 뷰가 처음 나타날 때 drawRect가 호출 된 후 내용이 캐시되며 스크롤은 GPU에 의해 수행되는 간단한 변환입니다. 복잡한 계층 구조가 아닌 단일 뷰를 다루기 때문에 UIKit의 drawRect 최적화는 덜 중요합니다. 병목 현상은 코어 그래픽 드로잉을 얼마나 최적화 할 수 있는지가됩니다.

가능하면 UIKit을 사용하십시오. 작동하는 가장 간단한 구현을 수행하십시오. 프로필. 인센티브가 있으면 최적화하십시오.


53

차이점은 UIView와 CALayer는 기본적으로 고정 이미지를 처리한다는 것입니다. 이 이미지는 그래픽 카드에 업로드됩니다 (OpenGL을 알고있는 경우 이미지를 텍스처로, UIView / CALayer를 이러한 텍스처를 나타내는 다각형으로 생각하십시오). 이미지가 GPU에 있으면 다른 이미지 위에 다양한 수준의 알파 투명도를 적용하더라도 매우 신속하고 심지어 여러 번, 그리고 약간의 성능 저하가 발생할 수 있습니다.

CoreGraphics / Quartz는 이미지 생성을 위한 API입니다 . 픽셀 버퍼 (다시 OpenGL 텍스처를 생각하십시오)를 가져 와서 그 안의 개별 픽셀을 변경합니다. 이 모든 것은 RAM과 CPU에서 발생하며 Quartz가 완료된 후에 만 ​​이미지가 GPU로 다시 흐려집니다. GPU에서 이미지를 가져 와서 변경 한 다음 전체 이미지 (또는 적어도 비교적 큰 청크)를 GPU에 다시 업로드하는이 왕복 여행은 다소 느립니다. 또한 Quartz가 수행하는 실제 드로잉은 실제로 수행하는 작업이 빠르지 만 GPU보다 느립니다.

GPU가 대부분 큰 덩어리로 변경되지 않은 픽셀 주위를 움직이고 있다는 점을 고려하면 분명합니다. Quartz는 픽셀의 랜덤 액세스를 수행하고 네트워킹, 오디오 등의 CPU를 공유합니다. 또한 Quartz를 사용하여 여러 요소를 동시에 그리는 경우 변경이있을 때 모든 요소를 ​​다시 그려야합니다. 하나의 이미지를 변경 한 다음 UIView 또는 CALayers가 다른 이미지에 붙여 넣으면 훨씬 적은 양의 데이터를 GPU에 업로드 할 수 있습니다.

-drawRect :를 구현하지 않으면 대부분의 뷰를 최적화 할 수 있습니다. 픽셀이 포함되어 있지 않으므로 아무것도 그릴 수 없습니다. UIImageView와 같은 다른 뷰는 UIImage 만 그립니다 (이 역시 GPU에 이미로드 된 텍스처에 대한 참조 임). 따라서 UIImageView를 사용하여 동일한 UIImage를 5 번 그리면 GPU에 한 번만 업로드 된 다음 5 개의 다른 위치에있는 디스플레이에 그려 지므로 시간과 CPU가 절약됩니다.

-drawRect :를 구현하면 새 이미지가 작성됩니다. 그런 다음 Quartz를 사용하여 CPU에서 그로 그립니다. drawRect에서 UIImage를 그리면 GPU에서 이미지를 다운로드하여 드로잉중인 이미지로 복사 한 다음 완료되면이 두 번째 이미지 사본 을 그래픽 카드로 다시 업로드 합니다. 따라서 장치에서 GPU 메모리를 두 배로 사용하고 있습니다.

따라서 그리는 가장 빠른 방법은 일반적으로 정적 내용을 변경 내용과 분리하는 것입니다 (별도의 UIViews / UIView 서브 클래스 / CALayers). 정적 컨텐츠를 UIImage로로드하고 UIImageView를 사용하여 작성하고 런타임에 동적으로 생성 된 컨텐츠를 drawRect에 넣습니다. 반복적으로 그려지는 컨텐츠가 있지만 자체적으로 변경되지 않는 경우 (즉, 일부 상태를 표시하기 위해 동일한 슬롯에 표시되는 3 개의 아이콘) UIImageView도 사용하십시오.

한 가지주의 사항 : 너무 많은 UIView가있는 것과 같은 것이 있습니다. 특히 투명한 영역은 표시 할 때 GPU 뒤에 다른 픽셀과 혼합되어야하기 때문에 GPU를 그리는 데 더 많은 비용이 듭니다. 그렇기 때문에 UIView를 "불투명"으로 표시하여 GPU가 해당 이미지 뒤에있는 모든 것을 지울 수 있음을 표시 할 수 있습니다.

런타임에 동적으로 생성되지만 응용 프로그램 수명 기간 동안 동일하게 유지되는 내용 (예 : 사용자 이름이 포함 된 레이블)이있는 경우 Quartz를 사용하여 텍스트와 함께 전체를 한 번만 그리는 것이 실제로 의미가 있습니다. 버튼 테두리 등을 배경의 일부로 그러나 이는 일반적으로 Instruments 앱이 다르게 지시하지 않는 한 필요하지 않은 최적화입니다.


자세한 답변 주셔서 감사합니다. 바라건대 더 많은 사람들이 아래로 스크롤하여 투표합니다.
Bob Spryn

나는 이것을 두 번 공표 할 수 있기를 바랍니다. 훌륭한 글을 보내 주셔서 감사합니다!
티벳의 바다 해안

약간의 보정은 대부분의 경우 전혀 없다 다운 은 GPU의 부하하지만 VRAM 빠르게로 느린 시스템 RAM에서 큰 픽셀 버퍼를 전송하기 때문에 업로드는 기본적으로 아직도 당신이 할 수있는 GPU를 요청할 수 있습니다 느린 작업입니다. OTOH는 일단 이미지가 있으면, 새로운 좌표 세트 (몇 바이트)를 GPU에 전송하는 것만으로도 그릴 위치를 알 수 있습니다.
uliwitness

@ uliwitness 나는 당신의 대답을 겪고 있었고 당신은 객체를 "불투명하게 그 이미지 뒤에있는 모든 것을 없애 버립니다"라고 언급했습니다. 그 부분에 약간의 빛을 비출 수 있습니까?
Rikh

@Rikh 레이어를 불투명으로 표시한다는 것은 a) 대부분의 경우 GPU가 해당 레이어 아래에 다른 레이어를 그리는 것을 방해하지 않으며, 적어도 레이어의 일부가 불투명 레이어 아래에있는 것을 방해하지 않습니다. 그린 후에도 실제로 알파 블렌딩을 수행하는 것이 아니라 화면에서 맨 위 레이어의 내용을 복사하기 만합니다 (일반적으로 불투명 레이어의 완전히 투명한 픽셀은 검은 색 또는 최소한 불투명 한 색상으로 표시됩니다)
uliwitness

26

나는 여기에 다른 사람들의 대답에서 외삽하는 것에 대한 요약을 유지하려고 노력할 것이며 원래 질문에 대한 업데이트로 명확한 질문을 할 것입니다. 그러나 나는 다른 사람들이 계속해서 답을 얻고 좋은 정보를 제공 한 사람들에게 투표하도록 권장합니다.

일반적인 접근

Ben Sandofsky가 그의 답변 에서 언급했듯이 일반적인 접근 방식 은 "UIKit을 사용할 수있을 때마다 가장 간단한 구현을 수행하십시오. 프로필. 인센티브가있을 때 최적화하십시오"는 것이 분명합니다.

  1. iDevice 에는 CPU와 GPU의 두 가지 주요 병목 현상이 있습니다.
  2. CPU는 뷰의 초기 드로잉 / 렌더링을 담당합니다.
  3. GPU는 대부분의 애니메이션 (Core Animation), 레이어 효과, 합성 등을 담당합니다.
  4. UIView에는 복잡한 뷰 계층 구조를 처리하기 위해 내장 된 많은 최적화, 캐싱 등이 있습니다.
  5. drawRect를 재정의하는 경우 UIView가 제공하는 많은 이점을 놓치게되며 일반적으로 UIView가 렌더링을 처리하도록하는 것보다 속도가 느립니다.

하나의 평면 UIView에서 셀 내용을 그리면 스크롤 테이블에서 FPS가 크게 향상 될 수 있습니다.

위에서 말했듯이 CPU와 GPU는 두 가지 가능한 병목 현상입니다. 그것들은 일반적으로 다른 것들을 처리하기 때문에 어떤 병목 현상에주의를 기울여야합니다. 테이블을 스크롤하는 경우 Core Graphics가 더 빨리 그려지는 것이 아니기 때문에 FPS를 크게 향상시킬 수 있습니다.

실제로 Core Graphics는 초기 렌더링의 중첩 UIView 계층보다 속도가 매우 느릴 수 있습니다. 그러나 고르지 않은 스크롤의 일반적인 이유는 GPU 병목 현상을 일으키는 것이므로이를 해결해야합니다.

drawRect를 재정의 (핵심 그래픽 사용)하면 테이블 스크롤에 도움이되는 이유

내가 이해 한 바에 따르면 GPU는 뷰의 초기 렌더링을 담당하지 않지만 렌더링 된 후 일부 레이어 속성이있는 텍스처 또는 비트 맵을 처리합니다. 그런 다음 비트 맵을 합성하고 해당 레이어에 영향을 미치는 모든 레이어와 대부분의 애니메이션 (코어 애니메이션)을 렌더링합니다.

테이블 뷰 셀의 경우 하나의 비트 맵에 애니메이션을 적용하는 대신 상위 뷰에 애니메이션을 적용하고 하위 뷰 레이아웃 계산, 레이어 효과 렌더링 및 모든 하위 뷰를 합성하기 때문에 복잡한 뷰 계층으로 GPU에 병목 현상이 발생할 수 있습니다. 따라서 하나의 비트 맵을 애니메이션하는 대신 동일한 픽셀 영역에서 여러 비트 맵의 ​​관계와 비트 맵의 ​​상호 작용 방식을 담당합니다.

요약하자면 코어 그래픽으로 하나의 뷰에서 셀을 그리는 이유는 테이블 스크롤 속도를 높일 수 있기 때문입니다. 그 이유는 더 빠르기 때문이 아니라 GPU의 부하를 줄이고 있기 때문에 특정 시나리오에서 문제를 일으키는 병목 현상입니다. .


1
그래도 조심하십시오. GPU는 각 프레임의 전체 레이어 트리를 효과적으로 재구성합니다. 따라서 레이어 이동은 사실상 제로 비용입니다. 하나의 큰 레이어를 만들면 한 레이어 만 이동하는 것이 여러 레이어를 이동하는 것보다 빠릅니다. 이 계층 내에서 무언가를 이동 하면 CPU가 갑자기 포함되며 비용이 발생합니다. 셀 내용은 일반적으로 많이 변경되지 않기 때문에 (비교적 드문 사용자 작업에 대한 응답으로 만), 더 적은 뷰를 사용하면 작업 속도가 빨라집니다. 그러나 모든 세포를 하나의 큰 관점으로 보는 것은 Bad Idea ™ 일 것입니다.
uliwitness

6

나는 게임 개발자이며 내 친구가 UIImageView 기반 뷰 계층이 게임 속도를 늦추고 끔찍하게 만들 것이라고 말했을 때 같은 질문을하고있었습니다. 그런 다음 UIViews, CoreGraphics, OpenGL 또는 Cocos2D와 같은 타사를 사용할지 여부에 대한 모든 것을 조사했습니다. WWDC의 친구, 교사 및 Apple 엔지니어로부터 얻은 일관된 대답 은 어느 정도 수준에서 모두 같은 일을 하기 때문에 결국 큰 차이가 없을 것이라는 이었습니다. UIViews와 같은 상위 수준 옵션은 CoreGraphics 및 OpenGL과 같은 하위 수준 옵션에 의존하므로 사용하기 쉽게 코드로 싸여 있습니다.

UIView를 다시 작성하려는 경우 CoreGraphics를 사용하지 마십시오. 그러나 모든 도면을 하나의 뷰에서 수행하는 한 CoreGraphics를 사용하면 약간의 속도를 얻을 수 있지만 실제로 가치 가 있습니까? 내가 찾은 대답은 대개 아니요입니다. 게임을 처음 시작할 때 iPhone 3G로 작업하고있었습니다. 게임이 복잡 해짐에 따라 약간의 지연이 발생하기 시작했지만 최신 장치에서는 완전히 눈에 띄지 않았습니다. 이제는 많은 작업이 진행되고 있으며 iPhone 4에서 가장 복잡한 수준으로 재생할 때 유일한 지연은 1-3fps 감소로 보입니다.

그래도 계측기를 사용하여 가장 시간이 많이 걸리는 기능을 찾기로 결정했습니다. 문제 가 UIViews 사용과 관련이 없다는 것을 알았습니다 . 대신 특정 충돌 감지 계산을 위해 CGRectMake를 반복 호출하고 하나의 중앙 스토리지 클래스에서 가져 오는 대신 동일한 이미지를 사용하는 특정 클래스에 대해 이미지 및 오디오 파일을 별도로로드했습니다.

결국 CoreGraphics를 사용하면 약간의 이익을 얻을 수 있지만 일반적으로 가치가 없거나 전혀 영향을 미치지 않을 수 있습니다. CoreGraphics를 사용하는 유일한 경우는 텍스트와 이미지가 아닌 기하학적 모양을 그릴 때입니다.


8
Core Animation과 Core Graphics를 혼동하는 것 같습니다. 코어 그래픽은 실제로 소프트웨어 기반의 느린 드로잉이며 drawRect 메소드를 재정의하지 않는 한 일반 UIView를 그리는 데 사용되지 않습니다. Core Animation은 하드웨어 가속으로 빠르고 화면에 표시되는 내용의 대부분을 담당합니다.
Nick Lockwood
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.