to drawRect ya da drawRect (ne zaman bir kişi drawRect / Core Graphics'i alt görüntülemelere / görüntülere karşı kullanmalı ve neden?)


142

Bu sorunun amacını açıklığa kavuşturmak için: Hem alt görünümlerle hem de drawRect kullanarak karmaşık görünümlerin NASIL oluşturulacağını biliyorum. Birini diğerinin üzerinde kullanmanın ne zaman ve neden olduğunu tam olarak anlamaya çalışıyorum.

Ayrıca, bunu çok önceden optimize etmenin ve herhangi bir profil oluşturmadan önce daha zor bir yol yapmanın mantıklı olmadığını anlıyorum. Her iki yöntemde de rahat olduğumu düşünün ve şimdi daha derin bir anlayış istiyorum.

Karışıklıkların çoğu, tablo görünümü kaydırma performansını gerçekten düzgün ve hızlı hale getirmeyi öğrenmekten geliyor. Tabii bu yöntemin orijinal kaynağı iPhone için twitter arkasındaki yazar (eski tweetie). Temelde tablo kaydırma tereyağını pürüzsüz hale getirmek için sırrın alt görünümleri KULLANMAMAK olduğunu, ancak bunun yerine tüm çizimleri tek bir özel uiview'de yapmak olduğunu söylüyor. Esasen, çok sayıda alt görünüm kullanmanın çok fazla ek yükü olduğu ve ana görünümleri üzerinde sürekli olarak yeniden birleştirildiği için oluşturmayı yavaşlattığı görülmektedir.

Adil olmak gerekirse, bu 3GS oldukça yeni bir marka spankin olduğunda yazıldı ve iDevices o zamandan beri çok daha hızlı oldu. Yine bu yöntem olan düzenli önerdi üzerinde interwebs ve başka yerlerde yüksek performanslı tablolar için. Aslında Apple'ın Tablo Örnek Kodunda önerilen bir yöntemdir, çeşitli WWDC videolarında ( iOS Geliştiricileri için Pratik Çizim ) ve birçok iOS programlama kitabında önerilmiştir .

Grafik tasarlamak ve onlar için Temel Grafik kodu oluşturmak için harika görünümlü araçlar bile var .

İlk başta "Core Graphics'in var olmasının bir nedeni var. HIZLI!"

Ama "Mümkün olduğunda Çekirdek Grafikleri Tercih Et" fikrini alır almaz, drawRect'in genellikle bir uygulamadaki zayıf yanıt vermekten sorumlu olduğunu, son derece pahalı bir bellek bilimi olduğunu ve CPU'ya gerçekten vergi verdiğini görmeye başlarım. Temel olarak, " drawRect'i geçersiz kılmaktan kaçınmalıyım " (WWDC 2012 iOS Uygulama Performansı: Grafikler ve Animasyonlar )

Yani sanırım her şey gibi karmaşık. Belki kendime ve başkalarına drawRect'i kullanmak için Ne Zaman ve Neden?

Core Graphics'i kullanmak için birkaç belirgin durum görüyorum:

  1. Dinamik verileriniz var (Apple'ın Hisse Senedi Grafiği örneği)
  2. Basit bir yeniden boyutlandırılabilir resim ile yürütülemeyen esnek bir kullanıcı arayüzü öğeniz var
  3. Oluşturulduktan sonra birden çok yerde kullanılan dinamik bir grafik oluşturuyorsunuz

Çekirdek Grafiklerden kaçınmak için durumlar görüyorum:

  1. Görünümünüzün özelliklerinin ayrı olarak canlandırılması gerekir
  2. Göreceli olarak küçük bir görünüm hiyerarşiniz var, bu nedenle CG kullanarak algılanan ekstra çabalar kazanmaya değmez
  3. Her şeyi yeniden çizmeden görünümün parçalarını güncellemek istiyorsunuz
  4. Üst görünüm boyutu değiştiğinde alt görünümlerinizin düzeninin güncellenmesi gerekir

Bu yüzden bilginizi verin. Hangi durumlarda drawRect / Core Graphics (alt görünümlerle de gerçekleştirilebilir) için ulaşırsınız? Sizi bu karara götüren faktörler nelerdir? Düzgün bir tablo hücresi kaydırması için özel bir görünümde çizim nasıl yapılır / neden önerilir, ancak Apple genel olarak performans nedenleriyle drawRect'e karşı tavsiyede bulunur? Basit arka plan resimleri ne olacak (yeniden boyutlandırılabilir bir png resmi kullanarak CG ile oluşturduğunuzda)?

Değerli uygulamalar yapmak için bu konunun derinlemesine anlaşılması gerekmeyebilir, ancak nedenlerini açıklayamadan teknikler arasında seçim yapmayı sevmiyorum. Beynim bana kızıyor.

Soru Güncelleme

Herkese bilgi için teşekkürler. Burada bazı açıklayıcı sorular:

  1. Çekirdek grafiklerle bir şeyler çiziyorsanız, ancak UIImageViews ve önceden oluşturulmuş bir png ile aynı şeyi başarabiliyorsanız, her zaman bu rotaya gitmeli misiniz?
  2. Benzer bir soru: Özellikle böyle badass araçlarıyla , çekirdek grafiklerde arayüz öğelerini ne zaman çizmeyi düşünmelisiniz? (Muhtemelen öğenizin görüntüsü değişken olduğunda. Örneğin 20 farklı renk varyasyonuna sahip bir düğme. Başka durumlar var mı?)
  3. Aşağıdaki cevabımdaki anlayışım göz önüne alındığında, bir tablo hücresi için aynı performans kazanımları, karmaşık UIView oluşturucunuzun kendisinden sonra hücrenizin bir anlık görüntü bitmap'ini etkin bir şekilde yakalayarak ve karmaşık görünümünüzü kaydırırken ve gizlerken göstererek elde edilebilir mi? Açıkçası bazı parçalar üzerinde çalışılmalıdır. Sadece ilginç bir düşünce vardı.

Yanıtlar:


72

Mümkün olduğunca UIKit ve alt görünümlere sadık kalın. Daha üretken olabilirsiniz ve işlerin bakımı daha kolay olması gereken tüm OO mekanizmalarından yararlanabilirsiniz. UIKit'ten ihtiyacınız olan performansı elde edemiyorsanız Çekirdek Grafikleri kullanın veya UIKit'te çizim efektlerini bir araya getirmeye çalışmanın daha karmaşık olacağını bilirsiniz.

Genel iş akışı, tablo görüntülerini alt görünümlerle oluşturmak olmalıdır. Uygulamanızın destekleyeceği en eski donanımdaki kare hızını ölçmek için Cihazlar'ı kullanın. 60 fps alamıyorsanız, CoreGraphics'e gidin. Bir süre bunu yaptığınızda, UIKit'in muhtemelen zaman kaybı olduğu konusunda bir fikir edinirsiniz.

Peki, Core Graphics neden hızlı?

CoreGraphics gerçekten hızlı değil. Her zaman kullanılıyorsa, muhtemelen yavaş gidiyorsunuzdur. GPU'ya yüklenen birçok UIKit çalışmasının aksine, CPU üzerinde çalışmasını gerektiren zengin bir çizim API'sı. Ekran boyunca hareket eden bir topu canlandırmak zorunda kalsaydınız, setNeedsDisplay'i saniyede 60 kez bir görünümde aramak korkunç bir fikir olurdu. Dolayısıyla, görünümünüzün tek tek canlandırılması gereken alt bileşenleriniz varsa, her bileşen ayrı bir katman olmalıdır.

Diğer sorun, drawRect ile özel çizim yapmadığınızda, UIKit stok görünümlerini optimize edebilir, böylece drawRect bir op-op değildir veya birleştirme ile kısayollar alabilir. DrawRect'i geçersiz kıldığınızda, UIKit'in yavaş yolu izlemesi gerekir, çünkü ne yaptığınız hakkında hiçbir fikri yoktur.

Bu iki problem, tablo görünümü hücreleri durumunda yararlardan daha ağır basabilir. Ekranda bir görünüm ilk görüntülendiğinde drawRect çağrıldıktan sonra, içerik önbelleğe alınır ve kaydırma GPU tarafından gerçekleştirilen basit bir çeviridir. Karmaşık bir hiyerarşi yerine tek bir görünümle uğraştığınız için, UIKit'in drawRect optimizasyonları daha az önemli hale gelir. Böylece darboğaz, Core Graphics çiziminizi ne kadar optimize edebileceğinize dönüşür.

Mümkün olduğunca UIKit kullanın. Çalışan en basit uygulamayı yapın. Profil. Bir teşvik olduğunda, optimize edin.


53

Fark, UIView ve CALayer'ın esasen sabit görüntülerle uğraşmasıdır. Bu görüntüler grafik kartına yüklenir (OpenGL'yi biliyorsanız, bir görüntüyü bir doku olarak düşünün ve bir UIView / CALayer'ı böyle bir dokuyu gösteren bir çokgen olarak düşünün). Bir görüntü GPU'da olduğunda, çok hızlı bir şekilde ve hatta birkaç kez ve (hafif bir performans cezasıyla) diğer görüntülerin üstünde değişen alfa saydamlığı düzeylerinde bile çizilebilir.

CoreGraphics / Quartz görüntü oluşturmak için kullanılan bir API'dir . Bir piksel arabelleği alır (yine OpenGL dokusunu düşünün) ve içindeki tek tek pikselleri değiştirir. Tüm bunlar RAM'de ve CPU'da gerçekleşir ve yalnızca Quartz yapıldıktan sonra görüntü GPU'ya "akıtılır". GPU'dan bir görüntü alma, değiştirme, ardından tüm görüntüyü (veya en azından nispeten büyük bir yığınını) GPU'ya geri yükleme turu oldukça yavaştır. Ayrıca, Quartz'ın yaptığı gerçek çizim, yaptığınız şey için gerçekten hızlı olsa da, GPU'nun yaptığından çok daha yavaştır.

GPU'nun büyük parçalarda çoğunlukla değişmemiş pikseller etrafında hareket ettiğini düşünüyoruz. Quartz, piksellerin rasgele erişimini yapar ve CPU'yu ağ, ses vb. İle paylaşır. Ayrıca, aynı anda Kuvars kullanarak çizdiğiniz birkaç öğeniz varsa, bir değişiklik olduğunda tümünü yeniden çizmeniz ve ardından bir parça değiştirir ve ardından UIViews veya CALayers'ın diğer resimlerinize yapıştırmasına izin verirseniz, GPU'ya çok daha az miktarda veri yüklemekten kurtulabilirsiniz.

-DrawRect: öğesini uygulamadığınızda, çoğu görünüm yalnızca optimize edilebilir. Herhangi bir piksel içermezler, bu nedenle hiçbir şey çizemezler. UIImageView gibi diğer görünümler yalnızca bir UIImage çizer (bu da yine GPU'ya zaten yüklenmiş olan bir dokuya bir referanstır). Dolayısıyla, aynı UIImage'ı bir UIImageView kullanarak 5 kez çizerseniz, GPU'ya yalnızca bir kez yüklenir ve ardından 5 farklı konumda ekrana çizilir, böylece bize zaman ve CPU tasarrufu sağlanır.

-DrawRect: uyguladığınızda yeni bir görüntü oluşturulur. Daha sonra Quartz kullanarak CPU'ya bunu çizersiniz. DrawRect'inize bir UIImage çizerseniz, muhtemelen görüntüyü GPU'dan indirir, çizdiğiniz görüntüye kopyalar ve işiniz bittiğinde görüntünün bu ikinci kopyasını tekrar grafik kartına yükler . Böylece cihazda iki kez GPU belleği kullanıyorsunuz.

Bu nedenle çizmenin en hızlı yolu genellikle statik içeriği değişen içerikten ayrı tutmaktır (ayrı UIViews / UIView alt sınıflarında / CALayers'da). Statik içeriği bir UIImage olarak yükleyin ve bir UIImageView kullanarak çizin ve dinamik olarak oluşturulan içeriği çalışma zamanında bir drawRect içine koyun. Tekrar tekrar çizilen içeriğiniz varsa, ancak kendi başına değişmezse (bazı durumları belirtmek için aynı yuvada gösterilen 3 simgeleri) UIImageView'ı da kullanın.

Bir uyarı: Çok fazla UIViews olması gibi bir şey var. Özellikle şeffaf alanlar çizmek için GPU üzerinde daha büyük bir ücret alır, çünkü görüntülendiğinde arkalarındaki diğer piksellerle karıştırılması gerekir. Bu nedenle GPU'ya bu görüntünün arkasındaki her şeyi yok edebileceğini belirtmek için bir UIView'i "opak" olarak işaretleyebilirsiniz.

Çalışma zamanında dinamik olarak oluşturulan ancak uygulamanın kullanım ömrü boyunca aynı kalan içeriğiniz varsa (örneğin, kullanıcı adını içeren bir etiket), metinle birlikte bir kez Quartz kullanarak her şeyi çizmek mantıklı olabilir. düğme kenarlığı vb. Ancak bu, Cihazlar uygulaması size farklı bir şekilde söylemediği sürece genellikle gerekli olmayan bir optimizasyondur.


Detaylı cevap için teşekkürler. Umarım daha fazla kişi aşağı kaydırır ve oy verir.
Bob Spryn

Bunu iki kez onaylayabilseydim. Bu muhteşem yazı için teşekkürler!
Tibet Deniz Kıyısı

Hafif düzeltme: Çoğu durumda GPU'dan aşağı yükleme yoktur , ancak yükleme hala temel olarak en yavaş işlemdir, çünkü GPU'dan daha yavaş sistem RAM'inden daha hızlı VRAM'a aktarması gerekir. Bir görüntü var olduğunda OTOH, onu hareket ettirmek sadece GPU'ya yeni bir koordinat kümesi (birkaç bayt) göndermeyi içerir, böylece iy nerede çizileceğini bilir.
uliwitness

@uliwitness Ben senin cevabından geçiyordum ve bir nesneyi "opak o görüntünün arkasındaki her şeyi yok eder" olarak işaretlemiştin. Lütfen o tarafa biraz ışık tutabilir misin?
Rikh

@Rikh Bir katmanı opak olarak işaretlemek, a) çoğu durumda, GPU'nun, en azından opak katmanın altında kalan kısımlarının, bu katmanın altına başka katmanlar çizmeyi rahatsız etmeyeceği anlamına gelir. Çizilmiş olsalar bile, aslında alfa harmanlaması yapmaz, sadece üst katmanın içeriğini ekrana kopyalar (genellikle opak bir katmanda tamamen şeffaf pikseller siyah veya renklerinin en azından opak bir versiyonuna dönüşür)
uliwitness

26

Burada başkalarının cevaplarından tahmin ettiklerimin bir özetini tutmaya çalışacağım ve orijinal soruya bir güncellemede açıklayıcı sorular soracağım. Ancak başkalarını, cevapları vermeye ve iyi bilgi verenleri oylamaya teşvik ediyorum.

Genel yaklaşım

Bu Ben Sandofsky belirtildiği gibi genel yaklaşım, gayet açıktır onun cevabı olmalı "Eğer kullanım UIKit. O çalışır. Profili. Bir teşvik var, optimize en basit uygulama yapın bir şey olduğunda."

Neden

  1. Bir iDevice'de iki ana olası darboğaz vardır: CPU ve GPU
  2. Bir görünümün ilk çiziminden / oluşturulmasından CPU sorumludur
  3. GPU, animasyon (Core Animation), katman efektleri, birleştirme vb. İşlemlerin çoğundan sorumludur.
  4. UIView, karmaşık görünüm hiyerarşilerini işlemek için birçok optimizasyon, önbellekleme vb.
  5. DrawRect'i geçersiz kılarken, UIView'in sağladığı birçok avantajı kaçırırsınız ve genellikle UIView'in oluşturmayı işlemesine izin vermekten daha yavaştır.

Düz bir UIView içinde hücre içeriği çizmek, kaydırma tablolarındaki FPS'nizi büyük ölçüde artırabilir.

Yukarıda söylediğim gibi, CPU ve GPU iki olası darboğazdır. Genellikle farklı şeyleri ele aldıkları için, hangi darboğazda koştuğunuza dikkat etmeniz gerekir. Kaydırma tabloları durumunda, Core Graphics'in daha hızlı çizim yapması değil ve bu yüzden FPS'nizi büyük ölçüde geliştirebilir.

Aslında, Core Graphics ilk oluşturma için iç içe UIView hiyerarşisinden çok daha yavaş olabilir. Bununla birlikte, dalgalı kaydırma için tipik neden, GPU'yu tıklatmanızdır, bu yüzden bunu ele almanız gerekir.

DrawRect'i (temel grafikleri kullanarak) geçersiz kılmak neden tablo kaydırmaya yardımcı olabilir:

Anladığım kadarıyla, GPU görünümlerin ilk oluşturulmasından sorumlu değildir, ancak oluşturulduktan sonra bazen bazı katman özelliklerinde dokular veya bitmapler verilir. Daha sonra bitmap'lerin birleştirilmesinden, tüm bu katman etkilerinin gösterilmesinden ve animasyonun çoğundan sorumludur (Çekirdek Animasyon).

Tablo görünümü hücreleri durumunda, GPU karmaşık görünüm hiyerarşileriyle tıkanabilir, çünkü bir bitmap'i canlandırmak yerine üst görünümü canlandırır ve alt görünüm düzen hesaplamaları yapar, katman efektleri oluşturur ve tüm alt görünümleri birleştirir. Bu nedenle, bir bitmap'i canlandırmak yerine, aynı piksel alanı için bir grup bitmap ilişkisinden ve bunların nasıl etkileştiklerinden sorumludur.

Özet olarak, hücrenizi temel grafiklerle tek bir görünümde çizmenin nedeni, tablo kaydırmanızı hızlandırabilmesinin nedeni, daha hızlı çizim yapması DEĞİLDİR, ancak GPU'daki yükü azalttığı için, bu belirli senaryoda size sıkıntı veren sıkıntı .


1
Yine de dikkatli ol. GPU'lar, her bir çerçeve için tüm katman ağacını etkili bir şekilde yeniden birleştirir. Bu nedenle, bir katmanı taşımak etkili bir şekilde sıfır maliyettir. Bir büyük katman oluşturursanız, yalnızca bu katmanı taşımak birkaç katmandan daha hızlıdır. Bu katmanın içine bir şey taşımak aniden CPU'yu içerir ve bir maliyeti vardır. Hücre içeriği genellikle çok fazla değişmediğinden (yalnızca nispeten nadir kullanıcı eylemlerine yanıt olarak), daha az görüntüleme kullanılması işleri hızlandırır. Ancak tüm hücrelerle geniş bir görüş elde etmek Bad Idea ™ olacaktır.
uliwitness

6

Ben bir oyun geliştiriciyim ve arkadaşım bana UIImageView tabanlı görünüm hiyerarşimin oyunumu yavaşlatacağını ve korkunç hale getireceğini söylediğinde aynı soruları soruyordum. Daha sonra UIViews, CoreGraphics, OpenGL veya Cocos2D gibi 3. bir şey kullanıp kullanmayacağımı bulabildiğim her şeyi araştırmaya başladım. WWDC'deki arkadaşlardan, öğretmenlerden ve Apple mühendislerinden aldığım tutarlı cevap, sonunda çok fazla bir fark olmayacağıydı, çünkü bir düzeyde hepsi aynı şeyi yapıyorlar . UIViews gibi daha yüksek düzeydeki seçenekler CoreGraphics ve OpenGL gibi daha düşük düzeydeki seçeneklere dayanır, yalnızca kullanımı kolaylaştırmak için kodla sarılırlar.

UIView'i yeniden yazacaksanız CoreGraphics'i kullanmayın. Ancak, tüm çiziminizi tek bir görünümde yaptığınız sürece CoreGraphics'i kullanmaktan biraz hız kazanabilirsiniz, ancak gerçekten buna değer mi? Bulduğum cevap genellikle hayır. Oyunuma ilk başladığımda iPhone 3G ile çalışıyordum. Oyunum karmaşıklaştıkça bazı gecikmeler görmeye başladım, ancak yeni cihazlarla tamamen farkedilemezdi. Şimdi çok fazla hareketim var ve tek gecikme bir iPhone 4'te en karmaşık seviyede oynarken 1-3 fps'de bir düşüş gibi görünüyor.

Yine de en çok zaman alan fonksiyonları bulmak için Aletleri kullanmaya karar verdim. Sorunların UIViews kullanımımla ilgili olmadığını gördüm . Bunun yerine, belirli çarpışma algılama hesaplamaları için tekrar tekrar CGRectMake'i çağırıyor ve aynı görüntüleri kullanan belirli sınıflar için tek bir merkezi depolama sınıfından çizim yapmak yerine görüntü ve ses dosyalarını ayrı olarak yüklüyordu.

Sonuç olarak, CoreGraphics'i kullanarak küçük bir kazanç elde edebilirsiniz, ancak genellikle buna değmez veya herhangi bir etkisi olmayabilir. CoreGraphics'i kullandığım tek zaman, metin ve görüntüler yerine geometrik şekiller çizerken.


8
Core Animation'ı Core Graphics ile karıştırıyor olabileceğinizi düşünüyorum. Core Graphics gerçekten yavaş, yazılım tabanlı çizimdir ve drawRect yöntemini geçersiz kılmadığınız sürece normal UIViews çizmek için kullanılmaz. Core Animation, donanım hızlandırmalı, ekranda gördüklerinizin çoğundan hızlı ve sorumlu.
Nick Lockwood
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.