drawRect o non drawRect (quando si dovrebbe usare drawRect / Core Graphics vs subview / immagini e perché?)


142

Per chiarire lo scopo di questa domanda: so COME creare viste complicate con entrambe le viste secondarie e usando drawRect. Sto cercando di comprendere appieno quando e perché usare l'uno sull'altro.

Capisco anche che non ha senso ottimizzare molto prima del tempo e fare qualcosa di molto più difficile prima di fare qualsiasi profilazione. Considera che mi sento a mio agio con entrambi i metodi e ora voglio davvero una comprensione più profonda.

Gran parte della mia confusione deriva dall'imparare a rendere le prestazioni di scorrimento della vista tabella davvero fluide e veloci. Naturalmente la fonte originale di questo metodo è dell'autore dietro twitter per iPhone (precedentemente tweetie). Fondamentalmente dice che per rendere più fluido lo scrolling delle tabelle, il segreto è NON usare le sottoview, ma invece fare tutto il disegno in un'unica vista personalizzata. Fondamentalmente sembra che l'uso di molte sottoview rallenta il rendering perché hanno un sacco di costi generali e sono costantemente ricomposti sulle loro visioni padre.

Ad essere onesti, questo è stato scritto quando il 3GS era piuttosto nuovo di zecca, e iDevices sono diventati molto più veloci da allora. Tuttavia questo metodo è regolarmente suggerito su interwebs e altrove per tabelle ad alte prestazioni. In realtà è un metodo suggerito nel Table Sample Code di Apple , è stato suggerito in diversi video del WWDC ( disegno pratico per sviluppatori iOS ) e in molti libri di programmazione iOS .

Ci sono anche strumenti fantastici per progettare la grafica e generare il codice Core Graphics per loro.

Quindi all'inizio sono portato a credere "esiste un motivo per cui esiste Core Graphics. È VELOCE!"

Ma non appena penso di avere l'idea "Favorisci la grafica di base quando possibile", comincio a vedere che drawRect è spesso responsabile della scarsa reattività in un'app, è estremamente costoso in termini di memoria e tassa davvero la CPU. Fondamentalmente, dovrei " Evitare di ignorare drawRect " ( Prestazioni delle app iOS del WWDC 2012 : grafica e animazioni )

Quindi immagino che, come tutto, sia complicato. Forse puoi aiutare me stesso e gli altri a capire quando e perché usare drawRect?

Vedo un paio di ovvie situazioni per usare Core Graphics:

  1. Hai dati dinamici (esempio di grafico azionario di Apple)
  2. Hai un elemento dell'interfaccia utente flessibile che non può essere eseguito con una semplice immagine ridimensionabile
  3. Stai creando un elemento grafico dinamico, che una volta eseguito il rendering viene utilizzato in più punti

Vedo situazioni per evitare Core Graphics:

  1. Le proprietà della vista devono essere animate separatamente
  2. Hai una gerarchia di vista relativamente piccola, quindi qualsiasi sforzo extra percepito usando CG non vale il guadagno
  3. Vuoi aggiornare parti della vista senza ridisegnare il tutto
  4. Il layout delle visualizzazioni secondarie deve essere aggiornato quando cambiano le dimensioni della visualizzazione padre

Quindi conferisci la tua conoscenza. In quali situazioni raggiungi drawRect / Core Graphics (che potrebbe essere realizzato anche con le sottoview)? Quali fattori ti portano a quella decisione? Come / Perché si consiglia di disegnare in una vista personalizzata per lo scorrimento delle celle di una tabella liscia e burrosa, ma Apple sconsiglia di disegnare per motivi di prestazioni in generale? Che dire delle semplici immagini di sfondo (quando le crei con CG vs usando un'immagine png ridimensionabile)?

Potrebbe non essere necessaria una profonda comprensione di questo argomento per creare app utili, ma non amo scegliere tra le tecniche senza essere in grado di spiegare il perché. Il mio cervello si arrabbia con me.

Aggiornamento domanda

Grazie per l'informazione a tutti. Alcune domande di chiarimento qui:

  1. Se stai disegnando qualcosa con una grafica di base, ma puoi ottenere la stessa cosa con UIImageViews e un png pre-renderizzato, dovresti sempre seguire questa strada?
  2. Una domanda simile: in particolare con strumenti tosta come questo , quando dovresti considerare di disegnare elementi dell'interfaccia nella grafica principale? (Probabilmente quando la visualizzazione del tuo elemento è variabile. Ad esempio un pulsante con 20 diverse varianti di colore. Altri casi?)
  3. Data la mia comprensione nella mia risposta di seguito, è possibile ottenere gli stessi guadagni in termini di prestazioni per una cella di tabella catturando in modo efficace un'istantanea bitmap della cella dopo il rendering UIView complesso stesso e visualizzandola mentre si scorre e si nasconde la vista complessa? Ovviamente alcuni pezzi dovrebbero essere elaborati. Ho avuto solo un pensiero interessante.

Risposte:


72

Attenersi a UIKit e alle sottoview ogni volta che è possibile. Puoi essere più produttivo e sfruttare tutti i meccanismi OO che dovrebbero essere più facili da mantenere. Usa Core Graphics quando non riesci a ottenere le prestazioni di cui hai bisogno da UIKit o sai che provare a hackerare gli effetti di disegno in UIKit sarebbe più complicato.

Il flusso di lavoro generale dovrebbe essere quello di costruire le visualizzazioni di tabella con visualizzazioni secondarie. Usa gli strumenti per misurare la frequenza dei fotogrammi sull'hardware meno recente supportato dalla tua app. Se non riesci a ottenere 60 fps, passa a CoreGraphics. Quando lo fai da un po ', hai un'idea di quando UIKit è probabilmente una perdita di tempo.

Quindi, perché la Core Graphics è veloce?

CoreGraphics non è molto veloce. Se viene usato tutto il tempo, probabilmente stai andando piano. È una ricca API di disegno, che richiede che il suo lavoro sia svolto sulla CPU, al contrario di molto lavoro di UIKit che viene scaricato nella GPU. Se dovessi animare una palla che si muove attraverso lo schermo, sarebbe una pessima idea chiamare setNeedsDisplay su una vista 60 volte al secondo. Quindi, se hai sottocomponenti della tua vista che devono essere animati individualmente, ogni componente dovrebbe essere un livello separato.

L'altro problema è che quando non si esegue il disegno personalizzato con drawRect, UIKit è in grado di ottimizzare le viste dello stock, quindi drawRect è una no-op o può prendere scorciatoie con la composizione. Quando esegui l'override di drawRect, UIKit deve prendere la strada lenta perché non ha idea di cosa stai facendo.

Questi due problemi possono essere compensati dai vantaggi nel caso di celle con vista tabella. Dopo che drawRect viene chiamato quando una vista appare per la prima volta sullo schermo, i contenuti vengono memorizzati nella cache e lo scorrimento è una semplice traduzione eseguita dalla GPU. Poiché hai a che fare con un'unica vista, piuttosto che con una gerarchia complessa, le ottimizzazioni drawRect di UIKit diventano meno importanti. Quindi il collo di bottiglia diventa quanto puoi ottimizzare il tuo disegno grafico principale.

Ogni volta che puoi, usa UIKit. Esegui l'implementazione più semplice che funzioni. Profilo. Quando c'è un incentivo, ottimizza.


53

La differenza è che UIView e CALayer si occupano essenzialmente di immagini fisse. Queste immagini vengono caricate sulla scheda grafica (se conosci OpenGL, pensa a un'immagine come una trama e un UIView / CALayer come un poligono che mostra tale trama). Una volta che un'immagine è nella GPU, può essere disegnata molto rapidamente, e anche più volte, e (con una leggera penalità prestazionale) anche con livelli variabili di trasparenza alfa sopra altre immagini.

CoreGraphics / Quartz è un'API per la generazione di immagini. Prende un buffer di pixel (di nuovo, pensa a texture OpenGL) e cambia i singoli pixel al suo interno. Tutto ciò accade nella RAM e nella CPU e solo una volta terminato il Quartz, l'immagine viene "ripulita" dalla GPU. Questo round-trip di ottenere un'immagine dalla GPU, modificarla, quindi caricare l'intera immagine (o almeno una parte relativamente grande di essa) di nuovo nella GPU è piuttosto lento. Inoltre, il disegno reale eseguito da Quartz, anche se molto veloce per quello che stai facendo, è molto più lento di quello che fa la GPU.

Questo è ovvio, considerando che la GPU si muove principalmente attorno a pixel invariati in grossi blocchi. Quartz accede in modo casuale ai pixel e condivide la CPU con rete, audio, ecc. Inoltre, se si hanno diversi elementi disegnati usando Quartz contemporaneamente, è necessario ridisegnarli tutti quando si cambia, quindi caricare il intero pezzo, mentre se cambi un'immagine e poi lasci che UIViews o CALayers lo incollino sulle altre immagini, puoi cavartela caricando quantità molto più piccole di dati nella GPU.

Quando non si implementa -drawRect :, la maggior parte delle visualizzazioni può essere semplicemente ottimizzata. Non contengono pixel, quindi non possono disegnare nulla. Altre viste, come UIImageView, disegnano solo un UIImage (che, di nuovo, è essenzialmente un riferimento a una trama, che probabilmente è già stata caricata sulla GPU). Quindi, se si disegna lo stesso UIImage 5 volte utilizzando UIImageView, viene caricato sulla GPU una sola volta, quindi disegnato sul display in 5 posizioni diverse, risparmiando tempo e CPU.

Quando si implementa -drawRect :, viene creata una nuova immagine. Disegnalo poi sulla CPU usando Quartz. Se si disegna un UIImage in drawRect, probabilmente scarica l'immagine dalla GPU, la copia nell'immagine su cui si sta disegnando e, una volta terminato, carica questa seconda copia dell'immagine sulla scheda grafica. Quindi stai usando il doppio della memoria GPU sul dispositivo.

Quindi il modo più veloce per disegnare è di solito di mantenere il contenuto statico separato dalla modifica del contenuto (in UIVview / sottoclassi UIView separate / CALayer). Carica contenuto statico come UIImage e disegnalo usando UIImageView e metti il ​​contenuto generato dinamicamente in fase di esecuzione in drawRect. Se hai contenuti che vengono disegnati ripetutamente, ma di per sé non cambia (cioè le icone 3 che vengono mostrate nello stesso slot per indicare un certo stato) usa anche UIImageView.

Un avvertimento: esiste una cosa come avere troppe UIVview. Le aree particolarmente trasparenti richiedono un pedaggio maggiore sulla GPU per disegnare, perché devono essere mescolate con altri pixel dietro di esse quando vengono visualizzate. Questo è il motivo per cui puoi contrassegnare un UIView come "opaco", per indicare alla GPU che può semplicemente cancellare tutto ciò che sta dietro quell'immagine.

Se hai contenuto generato dinamicamente in fase di runtime ma rimane lo stesso per la durata della vita dell'applicazione (ad es. Un'etichetta contenente il nome utente), potrebbe essere logico disegnare il tutto una volta usando Quartz, con il testo, il bordo pulsante ecc., come parte dello sfondo. Ma di solito è un'ottimizzazione che non è necessaria a meno che l'app Instruments non ti dica diversamente.


Grazie per la risposta dettagliata Si spera che più persone scorrano verso il basso e votino anche questo.
Bob Spryn,

Vorrei poter votare questo due volte. Grazie per l'ottimo commento!
Costa marittima del Tibet,

Leggera correzione: Nella maggior parte dei casi non c'è verso il basso carico dalla GPU, ma l'upload è ancora fondamentalmente l'operazione più lenta si può chiedere una GPU da fare, perché deve trasferire buffer di grandi dimensioni in pixel dal sistema più lento di RAM in più veloce VRAM. OTOH una volta che un'immagine è lì, spostarla implica solo l'invio di una nuova serie di coordinate (pochi byte) alla GPU in modo da sapere dove disegnare.
uliwitness,

@uliwitness stavo esaminando la tua risposta e hai menzionato la marcatura di un oggetto come "opaco cancella tutto ciò che si nasconde dietro quell'immagine". Puoi per favore far luce su quella parte?
Rikh,

@Rikh Contrassegnare uno strato come opaco significa che a) nella maggior parte dei casi, la GPU non si preoccuperà di disegnare altri strati sotto quello strato, beh, almeno le parti di essi che giacciono sotto lo strato opaco. Anche se sono stati disegnati, in realtà non eseguirà la fusione alfa, ma copierà semplicemente il contenuto del livello superiore sullo schermo (di solito i pixel completamente trasparenti in un livello opaco finiranno per diventare neri o almeno una versione opaca del loro colore)
uliwitness,

26

Proverò a tenere un riepilogo di ciò che sto estrapolando dalle risposte degli altri qui, e farò domande di chiarimento in un aggiornamento alla domanda originale. Ma incoraggio gli altri a mantenere le risposte e votare coloro che hanno fornito buone informazioni.

Approccio generale

È abbastanza chiaro che l'approccio generale, come menzionato da Ben Sandofsky nella sua risposta , dovrebbe essere "Ogni volta che puoi, usa UIKit. Fai l'implementazione più semplice che funzioni. Profilo. Quando c'è un incentivo, ottimizza".

Il perché

  1. Ci sono due principali possibili colli di bottiglia in un iDevice, la CPU e la GPU
  2. La CPU è responsabile del disegno / rendering iniziale di una vista
  3. La GPU è responsabile della maggior parte dell'animazione (Core Animation), degli effetti di livello, della composizione, ecc.
  4. UIView ha molte ottimizzazioni, memorizzazione nella cache, ecc. Integrate per la gestione di gerarchie di viste complesse
  5. Quando si esegue l'override di drawRect, si perdono molti dei vantaggi offerti da UIView ed è generalmente più lento rispetto a consentire a UIView di gestire il rendering.

Disegnare il contenuto delle celle in una UIView piatta può migliorare notevolmente il tuo FPS su tabelle scorrevoli.

Come ho detto sopra, CPU e GPU sono due possibili colli di bottiglia. Dal momento che generalmente gestiscono cose diverse, devi prestare attenzione a quale collo di bottiglia stai incontrando. Nel caso delle tabelle a scorrimento, non è che Core Graphics stia disegnando più velocemente, ed è per questo che può migliorare notevolmente il tuo FPS.

In effetti, Core Graphics potrebbe benissimo essere più lenta di una gerarchia UIView nidificata per il rendering iniziale. Tuttavia, sembra che il motivo tipico dello scrolling discontinuo sia il fatto che tu stia strozzando la GPU, quindi devi affrontarlo.

Perché l'override di drawRect (usando la grafica principale) può aiutare lo scorrimento delle tabelle:

Da quanto ho capito, la GPU non è responsabile per il rendering iniziale delle viste, ma viene invece consegnato trame o bitmap, a volte con alcune proprietà di livello, dopo che sono state renderizzate. È quindi responsabile della composizione delle bitmap, del rendering di tutti quegli effetti di layer e della maggior parte dell'animazione (Core Animation).

Nel caso delle celle della vista tabella, la GPU può essere strozzata con gerarchie di viste complesse, perché invece di animare una bitmap, sta animando la vista padre e facendo calcoli del layout della vista secondaria, il rendering degli effetti del livello e la composizione di tutte le viste secondarie. Quindi, invece di animare una bitmap, è responsabile della relazione di un gruppo di bitmap e di come interagiscono, per la stessa area di pixel.

Quindi, in sintesi, il motivo per cui disegnare la tua cella in una vista con la grafica principale può accelerare lo scorrimento della tabella NON è perché sta disegnando più velocemente, ma perché sta riducendo il carico sulla GPU, che è il collo di bottiglia che ti dà problemi in quel particolare scenario .


1
Attenzione però. Le GPU ricomposiscono efficacemente l'intero albero dei layer per ciascun frame. Quindi spostare uno strato è effettivamente a costo zero. Se crei un livello più grande, spostarlo solo è più veloce di spostarne diversi. Spostare qualcosa all'interno di questo livello coinvolge improvvisamente la CPU e ha un costo. Poiché i contenuti delle celle di solito non cambiano molto (solo in risposta a azioni dell'utente relativamente rare), l'utilizzo di un numero inferiore di visualizzazioni accelera le cose. Ma avere una visione ampia con tutte le celle sarebbe una Bad Idea ™.
uliwitness,

6

Sono uno sviluppatore di giochi e mi ponevo le stesse domande quando il mio amico mi disse che la mia gerarchia di visualizzazioni basata su UIImageView avrebbe rallentato il mio gioco e lo avrebbe reso terribile. Ho quindi proceduto alla ricerca di tutto ciò che potevo trovare sull'utilizzo di UIViews, CoreGraphics, OpenGL o qualcosa di terze parti come Cocos2D. La risposta coerente che ho ricevuto da amici, insegnanti e ingegneri Apple al WWDC è stata che alla fine non ci sarà molta differenza perché a un certo livello stanno tutti facendo la stessa cosa . Le opzioni di livello superiore come UIVview si basano su opzioni di livello inferiore come CoreGraphics e OpenGL, solo che sono racchiuse nel codice per facilitarne l'utilizzo.

Non usare CoreGraphics se finirai per riscrivere l'UIView. Tuttavia, puoi ottenere un po 'di velocità dall'utilizzo di CoreGraphics, purché esegui tutti i tuoi disegni in una vista, ma ne vale davvero la pena ? La risposta che ho trovato di solito è no. Quando ho iniziato il mio gioco, stavo lavorando con l'iPhone 3G. Man mano che il mio gioco cresceva in complessità, ho iniziato a vedere qualche ritardo, ma con i dispositivi più recenti era completamente impercettibile. Ora ho molta azione in corso e l'unico ritardo sembra essere un calo di 1-3 fps quando si gioca nel livello più complesso su un iPhone 4.

Tuttavia ho deciso di utilizzare gli strumenti per trovare le funzioni che impiegavano più tempo. Ho scoperto che i problemi non erano correlati al mio uso di UIViews . Invece, ha ripetutamente chiamato CGRectMake per determinati calcoli di rilevamento delle collisioni e il caricamento di file di immagini e audio separatamente per determinate classi che utilizzano le stesse immagini, piuttosto che farle trarre da una classe di archiviazione centrale.

Quindi, alla fine, potresti essere in grado di ottenere un leggero guadagno dall'utilizzo di CoreGraphics, ma di solito non ne varrà la pena o potrebbe non avere alcun effetto. L'unica volta che utilizzo CoreGraphics è quando disegno forme geometriche piuttosto che testo e immagini.


8
Penso che potresti confondere l'animazione di base con la grafica di base. Core Graphics è molto lenta, disegno basato su software e non viene utilizzato per disegnare UIVview regolari a meno che non si ignori il metodo drawRect. Core Animation è hardware accelerato, veloce e responsabile della maggior parte di ciò che vedi sullo schermo.
Nick Lockwood,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.