untuk drawRect atau tidak drawRect (kapan harus menggunakan drawRect / Core Graphics vs subviews / gambar dan mengapa?)

142

Untuk memperjelas tujuan pertanyaan ini: Saya tahu BAGAIMANA membuat tampilan rumit dengan kedua subview dan menggunakan drawRect. Saya mencoba untuk sepenuhnya memahami kapan dan mengapa harus menggunakan satu di atas yang lain.

Saya juga mengerti bahwa tidak masuk akal untuk mengoptimalkan sebanyak itu sebelumnya, dan melakukan sesuatu dengan cara yang lebih sulit sebelum melakukan profil. Anggaplah saya nyaman dengan kedua metode ini, dan sekarang benar-benar menginginkan pemahaman yang lebih dalam.

Banyak kebingungan saya berasal dari belajar cara membuat tampilan gulir tampilan tabel benar-benar halus dan cepat. Tentu saja sumber asli dari metode ini adalah dari penulis di balik twitter untuk iPhone (sebelumnya tweetie). Pada dasarnya ia mengatakan bahwa untuk membuat tabel bergulir buttery halus, rahasianya adalah TIDAK menggunakan subview, tetapi melakukan semua gambar dalam satu tampilan kustom. Pada dasarnya tampaknya menggunakan banyak subview memperlambat rendering turun karena mereka memiliki banyak overhead, dan terus-menerus disusun ulang atas pandangan orang tua mereka.

Agar adil, ini ditulis ketika 3GS cukup baru, dan iDevices telah menjadi jauh lebih cepat sejak itu. Tetap metode ini secara teratur disarankan pada jalinan dan di tempat lain untuk tabel kinerja tinggi. Sebenarnya itu adalah metode yang disarankan dalam Tabel Contoh Kode Apple , telah disarankan dalam beberapa video WWDC ( Gambar Praktis untuk Pengembang iOS ), dan banyak buku pemrograman iOS .

Bahkan ada alat yang tampak luar biasa untuk merancang grafis dan menghasilkan kode Core Graphics untuk mereka.

Jadi pada awalnya saya percaya bahwa "ada alasan mengapa Core Graphics ada. Ini CEPAT!"

Tetapi segera setelah saya pikir saya mendapatkan ide "Mendukung Core Graphics bila memungkinkan", saya mulai melihat bahwa drawRect sering bertanggung jawab atas respons yang buruk dalam suatu aplikasi, adalah ingatan yang sangat mahal, dan benar-benar membebani CPU. Pada dasarnya, saya harus " Menghindari override drawRect " ( Kinerja Aplikasi iOS WWDC 2012 : Grafik dan Animasi )

Jadi saya kira, seperti semuanya, ini rumit. Mungkin Anda bisa membantu diri sendiri dan orang lain memahami When and Why's untuk menggunakan drawRect?

Saya melihat beberapa situasi yang jelas untuk menggunakan Core Graphics:

  1. Anda memiliki data dinamis (contoh Bagan Saham Apple)
  2. Anda memiliki elemen UI yang fleksibel yang tidak dapat dieksekusi dengan gambar resizable sederhana
  3. Anda membuat grafik dinamis, yang pernah dirender digunakan di banyak tempat

Saya melihat situasi untuk menghindari Core Graphics:

  1. Properti tampilan Anda perlu dianimasikan secara terpisah
  2. Anda memiliki hierarki tampilan yang relatif kecil, sehingga setiap upaya ekstra yang dirasakan menggunakan CG tidak sebanding dengan keuntungannya
  3. Anda ingin memperbarui potongan tampilan tanpa menggambar ulang semuanya
  4. Tata letak subview Anda perlu diperbarui ketika ukuran tampilan induk berubah

Jadi berikan pengetahuan Anda. Dalam situasi apa Anda meraih drawRect / Core Graphics (yang juga bisa dicapai dengan subview)? Faktor-faktor apa yang mengarahkan Anda ke keputusan itu? Bagaimana / Mengapa menggambar dalam satu tampilan kustom disarankan untuk menggulirkan sel tabel halus buttery, namun Apple menyarankan drawRect untuk alasan kinerja secara umum? Bagaimana dengan gambar latar belakang sederhana (kapan Anda membuatnya dengan CG vs menggunakan gambar png yang dapat diubah ukurannya)?

Pemahaman mendalam tentang hal ini mungkin tidak diperlukan untuk membuat aplikasi yang bermanfaat, tetapi saya tidak suka memilih di antara teknik-teknik tanpa bisa menjelaskan alasannya. Otak saya marah pada saya.

Pembaruan Pertanyaan

Terima kasih atas informasinya semua orang. Beberapa pertanyaan klarifikasi di sini:

  1. Jika Anda menggambar sesuatu dengan grafis inti, tetapi dapat melakukan hal yang sama dengan UIImageViews dan png yang dirender sebelumnya, haruskah Anda selalu menempuh rute itu?
  2. Pertanyaan serupa: Terutama dengan alat badass seperti ini , kapan Anda harus mempertimbangkan menggambar elemen antarmuka dalam grafis inti? (Mungkin ketika tampilan elemen Anda variabel. Mis. Tombol dengan 20 variasi warna berbeda. Ada kasus lain?)
  3. Berdasarkan pemahaman saya dalam jawaban saya di bawah ini, dapatkah kinerja yang sama diperoleh untuk sel tabel mungkin diperoleh dengan secara efektif menangkap bitmap snapshot sel Anda setelah UIView membuat render Anda sendiri, dan menampilkannya sambil menggulir dan menyembunyikan tampilan kompleks Anda? Jelas beberapa potong harus dikerjakan. Hanya pemikiran menarik yang saya miliki.
Bob Spryn
sumber

Jawaban:

72

Tetap berpegang pada UIKit dan subview kapan saja Anda bisa. Anda bisa lebih produktif, dan manfaatkan semua mekanisme OO yang seharusnya lebih mudah dipertahankan. Gunakan Core Graphics ketika Anda tidak bisa mendapatkan kinerja yang Anda butuhkan dari UIKit, atau Anda tahu mencoba meretas bersama-sama menggambar efek di UIKit akan lebih rumit.

Alur kerja umum harus membangun tampilan tabel dengan subview. Gunakan Instrumen untuk mengukur laju bingkai pada perangkat keras tertua yang akan didukung aplikasi Anda. Jika Anda tidak bisa mendapatkan 60fps, drop down ke CoreGraphics. Ketika Anda sudah melakukan ini untuk sementara waktu, Anda akan tahu kapan UIKit mungkin hanya buang-buang waktu.

Jadi, mengapa Core Graphics cepat?

CoreGraphics tidak terlalu cepat. Jika digunakan terus menerus, Anda mungkin akan lambat. Ini adalah API gambar yang kaya, yang membutuhkan pekerjaannya dilakukan pada CPU, sebagai lawan dari banyak pekerjaan UIKit yang diturunkan ke GPU. Jika Anda harus menggerakkan bola bergerak melintasi layar, itu ide yang buruk untuk memanggil setNeedsDisplay pada tampilan 60 kali per detik. Jadi, jika Anda memiliki sub-komponen pandangan Anda yang perlu dianimasikan secara terpisah, setiap komponen haruslah merupakan lapisan yang terpisah.

Masalah lainnya adalah ketika Anda tidak melakukan menggambar kustom dengan drawRect, UIKit dapat mengoptimalkan tampilan stok sehingga drawRect adalah no-op, atau dapat mengambil jalan pintas dengan pengomposisian. Ketika Anda mengganti drawRect, UIKit harus mengambil jalan lambat karena tidak tahu apa yang Anda lakukan.

Dua masalah ini dapat melebihi manfaatnya dalam kasus sel tampilan tabel. Setelah drawRect dipanggil saat tampilan pertama kali muncul di layar, konten di-cache, dan pengguliran adalah terjemahan sederhana yang dilakukan oleh GPU. Karena Anda berhadapan dengan satu tampilan, bukan hierarki yang kompleks, optimasi drawRect UIKit menjadi kurang penting. Jadi bottleneck menjadi seberapa banyak Anda dapat mengoptimalkan gambar Core Graphics Anda.

Kapan pun Anda bisa, gunakan UIKit. Lakukan implementasi paling sederhana yang berhasil. Profil. Ketika ada insentif, optimalkan.

Ben Sandofsky
sumber
53

Perbedaannya adalah bahwa UIView dan CALayer pada dasarnya menangani gambar yang diperbaiki. Gambar-gambar ini diunggah ke kartu grafis (jika Anda tahu OpenGL, anggap gambar sebagai tekstur, dan UIView / CALayer sebagai poligon yang menunjukkan tekstur seperti itu). Begitu sebuah gambar ada di GPU, gambar itu dapat ditarik dengan sangat cepat, dan bahkan beberapa kali, dan (dengan sedikit penalti kinerja) bahkan dengan berbagai tingkat transparansi alfa di atas gambar lain.

CoreGraphics / Quartz adalah API untuk menghasilkan gambar. Dibutuhkan penyangga piksel (sekali lagi, pikirkan tekstur OpenGL) dan ubah piksel individual di dalamnya. Ini semua terjadi pada RAM dan pada CPU, dan hanya sekali Kuarsa selesai, apakah gambar "kembali" ke GPU. Perjalanan pulang pergi mendapatkan gambar dari GPU, mengubahnya, lalu mengunggah seluruh gambar (atau setidaknya sebagian besar dari itu) kembali ke GPU agak lambat. Juga, gambar aktual yang dilakukan Quartz, walaupun sangat cepat untuk apa yang Anda lakukan, jauh lebih lambat daripada apa yang dilakukan GPU.

Itu jelas, mengingat GPU sebagian besar bergerak di sekitar piksel yang tidak berubah dalam potongan besar. Quartz melakukan akses acak piksel dan membagikan CPU dengan jaringan, audio, dll. Selain itu, jika Anda memiliki beberapa elemen yang Anda gambar menggunakan Quartz secara bersamaan, Anda harus menggambar ulang semuanya ketika ada perubahan, kemudian unggah seluruh bagian, sementara jika Anda mengubah satu gambar dan kemudian membiarkan UIViews atau CALayers menempelkannya ke gambar lain, Anda bisa lolos dengan mengunggah jumlah data yang jauh lebih kecil ke GPU.

Ketika Anda tidak menerapkan -drawRect :, sebagian besar tampilan hanya dapat dioptimalkan. Mereka tidak mengandung piksel, jadi tidak bisa menggambar apa pun. Pandangan lain, seperti UIImageView, hanya menggambar UIImage (yang, sekali lagi, pada dasarnya adalah referensi ke tekstur, yang mungkin sudah dimuat ke GPU). Jadi jika Anda menggambar UIImage yang sama 5 kali menggunakan UIImageView, itu hanya diunggah ke GPU sekali, dan kemudian ditarik ke layar di 5 lokasi berbeda, menghemat waktu dan CPU kami.

Ketika Anda menerapkan -drawRect :, ini menyebabkan gambar baru dibuat. Anda kemudian menarik bahwa pada CPU menggunakan Quartz. Jika Anda menggambar UIImage di drawRect Anda, kemungkinan unduhan gambar dari GPU, menyalinnya ke gambar yang Anda gambar, dan setelah Anda selesai, unggah salinan kedua gambar ini kembali ke kartu grafis. Jadi Anda menggunakan memori GPU dua kali pada perangkat.

Jadi cara tercepat untuk menggambar biasanya memisahkan konten statis dari perubahan konten (dalam subclass UIViews / UIView terpisah / CALayers). Memuat konten statis sebagai UIImage dan menggambarnya menggunakan UIImageView dan menempatkan konten yang dihasilkan secara dinamis saat runtime di drawRect. Jika Anda memiliki konten yang digambar berulang kali, tetapi dengan sendirinya tidak berubah (Yaitu 3 ikon yang ditampilkan dalam slot yang sama untuk menunjukkan beberapa status) gunakan UIImageView juga.

Satu peringatan: Ada yang namanya memiliki terlalu banyak UIViews. Khususnya area transparan mengambil beban lebih besar pada GPU untuk menggambar, karena mereka perlu dicampur dengan piksel lain di belakangnya saat ditampilkan. Inilah sebabnya mengapa Anda dapat menandai UIView sebagai "buram", untuk menunjukkan kepada GPU bahwa itu hanya dapat melenyapkan segala sesuatu di belakang gambar itu.

Jika Anda memiliki konten yang dihasilkan secara dinamis saat runtime tetapi tetap sama selama masa pakai aplikasi (misalnya label yang berisi nama pengguna), mungkin masuk akal untuk menggambar semuanya setelah menggunakan Kuarsa, dengan teks, perbatasan tombol dll., sebagai bagian dari latar belakang. Tapi itu biasanya optimasi yang tidak diperlukan kecuali aplikasi Instruments memberi tahu Anda berbeda.

uliwitness
sumber
Terima kasih atas jawaban terinci. Semoga lebih banyak orang menggulir ke bawah dan memilih ini juga.
Bob Spryn
Seandainya saya bisa mengungguli ini dua kali. Terima kasih untuk luncuran yang bagus!
Pantai Laut Tibet
Koreksi sedikit: Dalam kebanyakan kasus tidak ada down load dari GPU, tetapi upload pada dasarnya masih merupakan operasi paling lambat yang dapat Anda minta GPU lakukan, karena harus mentransfer buffer piksel besar dari RAM sistem yang lebih lambat ke dalam VRAM yang lebih cepat. OTOH begitu ada gambar, memindahkannya hanya melibatkan pengiriman set koordinat baru (beberapa byte) ke GPU sehingga saya tahu di mana harus menggambar.
uliwitness
@ AndreWitness saya sedang memeriksa jawaban Anda dan Anda menyebutkan menandai sebuah objek sebagai "buram menghapuskan semua yang ada di balik gambar itu". Bisakah Anda memberi penjelasan pada bagian itu?
Rikh
@Rikh Menandai lapisan sebagai buram berarti bahwa a) dalam banyak kasus, GPU tidak akan repot menggambar lapisan lain di bawah lapisan itu, yah, setidaknya bagian dari mereka yang terletak di bawah lapisan buram. Bahkan jika mereka telah ditarik, itu tidak akan benar-benar melakukan alpha blending, tetapi akan lebih suka hanya menyalin konten lapisan atas pada layar (biasanya piksel yang sepenuhnya transparan dalam lapisan buram akan berakhir hitam atau setidaknya versi buram warna mereka)
uliwitness
26

Saya akan mencoba dan menyimpan ringkasan tentang apa yang saya ekstrapolasi dari jawaban orang lain di sini, dan mengajukan pertanyaan klarifikasi dalam pembaruan ke pertanyaan awal. Tetapi saya mendorong orang lain untuk terus memberikan jawaban dan memilih mereka yang telah memberikan informasi yang baik.

Pendekatan umum

Cukup jelas bahwa pendekatan umum, seperti yang disebutkan Ben Sandofsky dalam jawabannya , haruslah "Kapan pun Anda bisa, gunakan UIKit. Lakukan implementasi paling sederhana yang berhasil. Profil. Ketika ada insentif, optimalkan."

Mengapa

  1. Ada dua kemungkinan kemacetan utama di iDevice, CPU dan GPU
  2. CPU bertanggung jawab atas gambar / rendering tampilan awal
  3. GPU bertanggung jawab atas sebagian besar animasi (Core Animation), efek layer, compositing, dll.
  4. UIView memiliki banyak optimisasi, caching, dll, dibangun untuk menangani hierarki tampilan yang kompleks
  5. Saat mengganti drawRect, Anda kehilangan banyak manfaat yang disediakan UIView, dan umumnya lebih lambat daripada membiarkan UIView menangani rendering.

Menggambar isi sel dalam satu flat UIView dapat sangat meningkatkan FPS Anda pada tabel bergulir.

Seperti yang saya katakan di atas, CPU dan GPU adalah dua kemungkinan kemacetan. Karena mereka umumnya menangani hal-hal yang berbeda, Anda harus memperhatikan kemacetan yang Anda hadapi. Dalam hal menggulir tabel, Core Graphics tidak bergerak lebih cepat, dan itulah sebabnya ia dapat sangat meningkatkan FPS Anda.

Bahkan, Core Graphics mungkin lebih lambat daripada hierarki UIView yang bersarang untuk render awal. Namun, tampaknya alasan khas untuk pengguliran berombak adalah Anda menghambat GPU, jadi Anda perlu mengatasinya.

Mengapa mengganti drawRect (menggunakan grafis inti) dapat membantu menggulir tabel:

Dari apa yang saya pahami, GPU tidak bertanggung jawab atas rendering tampilan awal, tetapi alih-alih menyerahkan tekstur, atau bitmap, kadang-kadang dengan beberapa properti layer, setelah mereka dibuat. Ia kemudian bertanggung jawab untuk mengkomposisikan bitmap, merender semua layer yang mempengaruhi, dan sebagian besar animasi (Core Animation).

Dalam kasus sel tampilan tabel, GPU dapat mengalami bottlenecked dengan hierarki tampilan yang kompleks, karena alih-alih menggerakkan satu bitmap, itu adalah menganimasikan tampilan induk, dan melakukan perhitungan tata letak subview, memberikan efek layer, dan menyusun semua subview. Jadi alih-alih menjiwai satu bitmap, ia bertanggung jawab atas hubungan sekelompok bitmap, dan bagaimana mereka berinteraksi, untuk area piksel yang sama.

Jadi secara ringkas, alasan menggambar sel Anda dalam satu tampilan dengan grafis inti dapat mempercepat pengguliran tabel Anda BUKAN karena menggambar lebih cepat, tetapi karena mengurangi beban pada GPU, yang merupakan hambatan yang membuat Anda kesulitan dalam skenario tertentu .

Bob Spryn
sumber
1
Hati-hati. GPU secara efektif menggabungkan ulang seluruh lapisan pohon untuk setiap frame. Jadi memindahkan layer secara efektif adalah nol biaya. Jika Anda membuat satu lapisan besar, hanya memindahkan satu lapisan lebih cepat daripada memindahkan beberapa. Memindahkan sesuatu di dalam lapisan ini tiba-tiba melibatkan CPU dan memiliki biaya. Karena konten sel biasanya tidak banyak berubah (hanya sebagai respons terhadap tindakan pengguna yang relatif jarang terjadi), menggunakan lebih sedikit tampilan mempercepat berbagai hal. Tetapi membuat satu tampilan besar dengan semua sel akan menjadi Ide Buruk ™.
uliwitness
6

Saya seorang pengembang game, dan saya mengajukan pertanyaan yang sama ketika teman saya mengatakan kepada saya bahwa hierarki tampilan berbasis UIImageView akan memperlambat permainan saya dan membuatnya mengerikan. Saya kemudian melanjutkan untuk meriset segala yang dapat saya temukan tentang apakah akan menggunakan UIViews, CoreGraphics, OpenGL atau sesuatu yang pihak ketiga seperti Cocos2D. Jawaban konsisten yang saya dapatkan dari teman, guru, dan insinyur Apple di WWDC adalah bahwa pada akhirnya tidak akan ada banyak perbedaan karena pada tingkat tertentu mereka semua melakukan hal yang sama . Opsi tingkat yang lebih tinggi seperti UIViews mengandalkan opsi tingkat yang lebih rendah seperti CoreGraphics dan OpenGL, hanya saja mereka dibungkus dengan kode untuk membuatnya lebih mudah untuk Anda gunakan.

Jangan gunakan CoreGraphics jika Anda hanya akan menulis ulang UIView. Namun, Anda bisa mendapatkan kecepatan dari menggunakan CoreGraphics, selama Anda melakukan semua gambar Anda dalam satu tampilan, tetapi apakah itu benar - benar layak ? Jawaban yang saya temukan biasanya tidak. Ketika saya pertama kali memulai permainan, saya bekerja dengan iPhone 3G. Ketika permainan saya tumbuh dalam kompleksitas, saya mulai melihat beberapa kelambatan, tetapi dengan perangkat yang lebih baru itu sama sekali tidak terlihat. Sekarang saya memiliki banyak aksi yang sedang berlangsung, dan satu-satunya kelambatan sepertinya adalah penurunan 1-3 fps ketika bermain di level paling kompleks pada iPhone 4.

Tetap saya memutuskan untuk menggunakan Instrumen untuk menemukan fungsi yang paling banyak memakan waktu. Saya menemukan bahwa masalahnya tidak terkait dengan penggunaan UIViews . Sebaliknya, itu berulang kali memanggil CGRectMake untuk perhitungan penginderaan tabrakan tertentu dan memuat file gambar dan audio secara terpisah untuk kelas-kelas tertentu yang menggunakan gambar yang sama, daripada meminta mereka menggambar dari satu kelas penyimpanan pusat.

Jadi pada akhirnya, Anda mungkin dapat mencapai sedikit keuntungan dari menggunakan CoreGraphics, tetapi biasanya itu tidak akan sepadan atau mungkin tidak memiliki efek sama sekali. Satu-satunya waktu saya menggunakan CoreGraphics adalah ketika menggambar bentuk geometris daripada teks dan gambar.

WolfLink
sumber
8
Saya pikir Anda mungkin membingungkan Core Animation dengan Core Graphics. Grafik Inti sangat lambat, gambar berbasis perangkat lunak dan tidak digunakan untuk menggambar UIViews biasa kecuali Anda mengganti metode drawRect. Core Animation adalah perangkat keras yang dipercepat, cepat dan bertanggung jawab untuk sebagian besar dari apa yang Anda lihat di layar.
Nick Lockwood