aby drawRect lub nie drawRect (kiedy należy użyć drawRect / Core Graphics vs podviews/images i dlaczego?)

Aby wyjaśnić cel tego pytania: wiem, jak tworzyć skomplikowane widoki zarówno z podwidywami, jak i za pomocą drawRect. Staram się w pełni zrozumieć, kiedy i dlaczego używać jednego nad drugim.

Rozumiem również, że nie ma sensu optymalizować tego z wyprzedzeniem i zrobić coś trudniejszego przed zrobieniem jakiegokolwiek profilowania. Zastanów się, że jestem zadowolony z obu metod, a teraz naprawdę chcę głębszego zrozumienia.

A lot of my zamieszanie wynika z nauki, jak sprawić, by wydajność przewijania w widoku tabeli była naprawdę płynna i szybka. Oczywiście oryginalne źródło tej metody pochodzi od autora za Twitterem dla iPhone ' a (dawniej tweetie). zasadniczo mówi, że aby przewijanie tabeli było płynne, sekret polega na tym, aby nie używać podglądów podrzędnych, ale zamiast tego robić wszystkie rysunki w jednym niestandardowym uiview. zasadniczo wygląda na to, że używanie wielu podwidywań spowalnia renderowanie, ponieważ mają one wiele narzutu i są stale na nowo komponowane przez swoich rodziców poglądy.

Aby być uczciwym, napisano to, gdy 3GS był całkiem nowy, a iDevices stały się znacznie szybsze od tego czasu. Nadal ta metoda jest regularnie sugerowane na interwebs i gdzie indziej dla tabel o wysokiej wydajności. W rzeczywistości jest to sugerowana metoda w przykładowy kod tabeli Apple , została zasugerowana w kilku filmach WWDC (praktyczny rysunek dla programistów iOS ) i wiele książek programistycznych iOS .

Istnieją nawet niesamowicie wyglądające narzędzia do projektowania grafiki i generowania dla nich kodu graficznego.

Więc na początku jestem prowadzić do przekonania " istnieje powód, dlaczego Core Graphics istnieje. Szybko!"

Ale jak tylko myślę, że mam pomysł "faworyzować Core Graphics, gdy to możliwe", zaczynam widzieć, że drawRect jest często odpowiedzialny za słabą responsywność w aplikacji, jest bardzo drogie pamięci mądry, i naprawdę opodatkowanie CPU. Zasadniczo, że powinienem " unikać nadpisywania drawRect "(WWDC 2012 wydajność aplikacji na iOS: Grafika i animacje)

Więc myślę, że jak wszystko, to jest skomplikowane. Może możesz pomóc sobie i innym zrozumieć, kiedy i dlaczego warto używać drawRect?

Widzę kilka oczywistych sytuacji, aby użyć grafiki Core:

    Dane dynamiczne (przykład wykresu giełdowego Apple ' a)]}
  1. masz elastyczny element interfejsu użytkownika, którego nie można wykonać za pomocą prosty obraz z możliwością zmiany rozmiaru
  2. tworzysz dynamiczną grafikę, która po renderowaniu jest używana w wielu miejscach

Widzę sytuacje, w których należy unikać podstawowych grafik:

  1. właściwości widoku muszą być animowane oddzielnie
  2. Masz stosunkowo małą hierarchię widoków, więc każdy dodatkowy wysiłek przy użyciu CG nie jest wart zysku]}
  3. chcesz zaktualizować fragmenty widoku bez przerysowywania całości
  4. układ podwidywań musi aktualizacja po zmianie rozmiaru widoku nadrzędnego

Więc obdarz swoją wiedzą. W jakich sytuacjach sięgasz po grafikę drawRect/Core (która może być również realizowana przy podwidywaniach)? Jakie czynniki doprowadziły cię do tej decyzji? Jak / Dlaczego rysowanie w jednym widoku niestandardowym jest zalecane do płynnego przewijania komórek tabeli, jednak Apple odradza drawRect ze względów wydajnościowych w ogóle? A co z prostymi obrazami tła (kiedy tworzysz je za pomocą CG vs używając png z możliwością zmiany rozmiaru obraz)?

Głębokie zrozumienie tego tematu może nie być potrzebne, aby tworzyć wartościowe aplikacje, ale nie lubię wybierać między technikami, nie będąc w stanie wyjaśnić dlaczego. Mój mózg się na mnie wkurza.

Aktualizacja Pytania

Dzięki wszystkim za informacje. Niektóre pytania wyjaśniające tutaj:

  1. jeśli rysujesz coś z podstawową grafiką, ale możesz osiągnąć to samo z UIImageViews i wstępnie renderowanym png, zawsze powinieneś to zrobić trasa?
  2. podobne pytanie: szczególnie w przypadku takich hardkorowych narzędzi, Kiedy należy rozważyć rysowanie elementów interfejsu w grafice core? (Prawdopodobnie, gdy wyświetlanie twojego elementu jest zmienne. np. przycisk z 20 różnymi wariantami kolorów. Jakieś inne sprawy?)
  3. biorąc pod uwagę moje zrozumienie w mojej Odpowiedzi poniżej, Czy można uzyskać taki sam wzrost wydajności komórki tabeli poprzez skuteczne przechwytywanie bitmapy migawki komórki po złożonym renderowaniu UIView i wyświetlanie tego podczas przewijania i ukrywania złożonego widoku? Oczywiście niektóre elementy trzeba by dopracować. Ciekawa myśl.
Author: Bob Spryn, 2013-02-02

4 answers

Trzymaj się UIKit i podglądów, kiedy tylko możesz. Możesz być bardziej produktywny i korzystać ze wszystkich mechanizmów OO, które powinny być łatwiejsze w utrzymaniu. Użyj Core Graphics, gdy nie możesz uzyskać wymaganej wydajności z UIKit lub wiesz, że próba zhakowania efektów rysowania w UIKit byłaby bardziej skomplikowana.

Ogólnym obiegiem pracy powinno być budowanie tableviews z podviews. Użyj narzędzi do pomiaru liczby klatek na najstarszym sprzęcie, który obsługuje Twoja aplikacja. Jeśli nie możesz uzyskać 60 klatek na sekundę, przejdź do CoreGraphics. Kiedy robisz to przez jakiś czas, masz poczucie, że UIKit jest prawdopodobnie stratą czasu.

Dlaczego Core Graphics jest szybki?

CoreGraphics nie jest zbyt szybki. Jeśli jest używany przez cały czas, prawdopodobnie idziesz powoli. Jest to rich drawing API, które wymaga, aby jego praca była wykonywana na CPU, w przeciwieństwie do wielu prac UIKit, które są odciążane do GPU. Jeśli trzeba było animować piłkę poruszającą się po ekranie, byłoby to okropny pomysł, aby wywołać setNeedsDisplay na widoku 60 razy na sekundę. Jeśli więc w widoku są komponenty podrzędne, które muszą być indywidualnie animowane, każdy komponent powinien być osobną warstwą.

Inny problem polega na tym, że jeśli nie wykonujesz niestandardowego rysowania za pomocą drawRect, UIKit może zoptymalizować widoki zapasów, więc drawRect jest no-op lub może używać skrótów z compositing. Po nadpisaniu drawRect, UIKit musi podążać powolną ścieżką, ponieważ nie ma pojęcia, co robisz.

Te korzyści w przypadku komórek widoku tabeli mogą przeważać nad dwoma problemami. Po wywołaniu drawRect, gdy Widok pojawia się po raz pierwszy na ekranie, zawartość jest buforowana, a przewijanie jest prostym tłumaczeniem wykonywanym przez GPU. Ponieważ masz do czynienia z pojedynczym widokiem, a nie złożoną hierarchią, optymalizacje drawRect UIKit stają się mniej ważne. Tak więc wąskim gardłem staje się to, jak bardzo możesz zoptymalizować swój rdzeń rysunku graficznego.

Kiedy tylko możesz, użyj UIKit. Zrób najprostszy wdrożenie, które działa. Profil. Gdy jest zachęta, Optymalizuj.

 69
Author: Ben Sandofsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-02 18:32:34

Różnica polega na tym, że UIView i CALayer zasadniczo zajmują się stałymi obrazami. Obrazy te są przesyłane do karty graficznej (jeśli znasz OpenGL, pomyśl o obrazie jako teksturze, a UIView/CALayer jako wielokąt pokazujący taką teksturę). Gdy obraz znajduje się na GPU, można go narysować bardzo szybko, a nawet kilka razy i (z niewielką karą za wydajność) nawet przy różnym poziomie przezroczystości alfa na innych obrazach.

CoreGraphics / Quartz jest API dla generowanie obrazów. Pobiera bufor pikseli (ponownie pomyśl o teksturze OpenGL)i zmienia poszczególne piksele w nim. To wszystko dzieje się w pamięci RAM i na procesorze, a dopiero po wykonaniu kwarcu obraz zostaje" spłukany " z powrotem do GPU. Ta runda pobierania obrazu z GPU, zmiany go, a następnie przesyłanie całego obrazu (lub przynajmniej stosunkowo dużej jego części) z powrotem do GPU jest raczej powolna. Ponadto, rzeczywisty rysunek, który robi Quartz, podczas gdy naprawdę szybki dla tego, co robisz, jest znacznie wolniejszy niż to, co robi GPU.

To oczywiste, biorąc pod uwagę, że GPU porusza się głównie po niezmienionych pikselach w dużych kawałkach. Quartz ma losowy dostęp do pikseli i dzieli procesor z siecią, audio itp. Ponadto, jeśli masz kilka elementów, które rysujesz za pomocą kwarcu w tym samym czasie, musisz ponownie narysować je wszystkie, gdy jeden się zmieni, a następnie przesłać cały fragment, podczas gdy jeśli zmienisz jeden obraz, a następnie pozwolisz UIViews lub CALayers wkleić go na inne obrazy, możesz uciec z przesyłaniem znacznie mniejszych ilości danych do GPU.

Gdy nie zaimplementujesz-drawRect:, większość widoków można po prostu zoptymalizować. Nie zawierają żadnych pikseli, więc nie mogą nic narysować. Inne widoki, takie jak UIImageView, rysują tylko UIImage (który, ponownie, jest zasadniczo odniesieniem do tekstury, która prawdopodobnie została już załadowana na GPU). Więc jeśli narysujesz ten sam UIImage 5 razy za pomocą interfejsu UIImageView, zostanie on przesłany do GPU tylko raz, a następnie narysowany na wyświetlaczu w 5 różne lokalizacje, oszczędzając nam czas i procesor.

Gdy zaimplementujesz -drawRect:, spowoduje to utworzenie nowego obrazu. Następnie rysujesz to na procesorze za pomocą kwarcu. Jeśli narysujesz interfejs użytkownika w aplikacji drawRect, prawdopodobnie pobierze on obraz z procesora graficznego, skopiuje go na obraz, na którym rysujesz, a gdy skończysz, załaduje tę drugą kopię obrazu z powrotem do karty graficznej. Więc używasz dwukrotnie więcej pamięci GPU na urządzeniu.

Więc najszybszym sposobem na losowanie jest zazwyczaj, aby zachować zawartość statyczną oddzieloną od zmieniającej się zawartości (w osobnych podklasach UIView/UIView/CALayers). Załaduj statyczną zawartość jako interfejs i narysuj ją za pomocą interfejsu UIImageView, a następnie umieść zawartość generowaną dynamicznie w czasie wykonywania w interfejsie drawRect. Jeśli masz zawartość, która jest rysowana wielokrotnie, ale sama w sobie nie zmienia się (tj. 3 ikony, które są wyświetlane w tym samym gnieździe, aby wskazać jakiś status), użyj UIImageView również.

Jedno zastrzeżenie: istnieje coś takiego jak posiadanie zbyt wielu przeglądów. Szczególnie przezroczyste obszary mają większy wpływ na procesor graficzny, ponieważ muszą być mieszane z innymi pikselami za nimi, gdy są wyświetlane. Dlatego możesz oznaczyć UIView jako "nieprzezroczysty", aby wskazać GPU, że może on po prostu zatrzeć wszystko za tym obrazem.

Jeśli masz zawartość, która jest generowana dynamicznie w czasie wykonywania, ale pozostaje taka sama przez cały okres życia aplikacji (np. etykietę zawierającą Nazwę użytkownika), może mieć sens po prostu narysować całość raz za pomocą kwarcu, z tekstem, obramowaniem przycisku itp., jako część tła. Ale zazwyczaj jest to optymalizacja, która nie jest potrzebna, chyba że aplikacja Instruments mówi inaczej.

 50
Author: uliwitness,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-26 02:36:39

Postaram się zachować podsumowanie tego, co ekstrapoluję na podstawie odpowiedzi innych i zadam pytania wyjaśniające w aktualizacji do pierwotnego pytania.[[2]} ale zachęcam innych do ciągłych odpowiedzi i głosowania na tych, którzy dostarczyli dobrych informacji.

Podejście Ogólne

Jest całkiem jasne, że ogólne podejście, jak wspomniał Ben Sandofsky w swojej odpowiedzi, powinno brzmieć " Kiedy tylko możesz, użyj UIKit. Wykonaj najprostszą implementację, która działa. Profil. Gdy jest zachęta, Optymalizuj."

Dlaczego

    W iDevice istnieją dwa główne wąskie gardła, CPU i GPU Procesor jest odpowiedzialny za wstępne rysowanie / renderowanie widoku]}
  1. GPU odpowiada za większość animacji (Core Animation), efekty warstw, komponowanie itp.
  2. UIView ma wiele optymalizacji, buforowania itp., wbudowanych do obsługi złożonych hierarchii widoków]}
  3. gdy nadpisanie drawRect tracisz wiele korzyści, które zapewnia UIView, i ogólnie jest wolniejszy niż pozwala UIView obsługiwać renderowanie.

Rysowanie zawartości komórek w jednym płaskim UIView może znacznie poprawić liczbę klatek na sekundę na tablicach przewijania.

Jak powiedziałem powyżej, CPU i GPU to dwa możliwe wąskie gardła. Ponieważ zazwyczaj zajmują się różnymi rzeczami, musisz zwrócić uwagę na to, z którym wąskim gardłem masz do czynienia. w przypadku przewijania tabel nie jest to Core Graphics rysuje się szybciej, dlatego może znacznie poprawić liczbę klatek na sekundę.

W rzeczywistości grafika rdzeniowa może być wolniejsza niż zagnieżdżona hierarchia UIView dla początkowego renderowania. Wydaje się jednak, że typowym powodem niestabilnego przewijania jest wąskie gardło GPU, więc musisz się tym zająć.

Dlaczego nadpisanie drawRect (przy użyciu grafiki core) może pomóc w przewijaniu tabeli:

Z tego co rozumiem, GPU nie odpowiada za początkowe renderowanie widoków, ale zamiast tego jest przekazywane tekstury lub bitmapy, czasami z niektórymi właściwościami warstw, po ich wyrenderowaniu. Następnie jest odpowiedzialny za komponowanie bitmap, renderowanie wszystkich tych warstw i większość animacji (Core Animation).

W przypadku komórek widoku tabeli, GPU może być wąskie za pomocą złożonych hierarchii widoków, ponieważ zamiast animować jedną bitmapę, animuje widok nadrzędny i wykonuje obliczenia układu widoku podrzędnego, renderuje efekty warstw i komponowanie wszystkich podwidywań. Zamiast animować jedną bitmapę, jest ona odpowiedzialna za relacje kilku bitmap i sposób ich interakcji dla tego samego obszaru pikseli.

Podsumowując, powodem, dla którego rysowanie komórki w jednym widoku z rdzeniem graficznym może przyspieszyć przewijanie tabeli, nie jest to, że rysuje się szybciej, ale dlatego, że zmniejsza obciążenie GPU, które jest wąskim gardłem, dając problemy w tym konkretnym scenariuszu.

 26
Author: Bob Spryn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:07

Jestem twórcą gier i zadawałem te same pytania, Kiedy mój przyjaciel powiedział mi, że moja hierarchia widoku oparta na UIImageView spowolni moją grę i sprawi, że będzie straszna. Następnie przystąpiłem do badania wszystkiego, co mogłem znaleźć na temat tego, czy używać UIViews, CoreGraphics, OpenGL lub czegoś innego, takiego jak Cocos2D. spójna odpowiedź, którą otrzymałem od przyjaciół, nauczycieli i inżynierów Apple w WWDC, była taka, że nie będzie dużej różnicy w końcu, ponieważ na pewnym poziomie są wszyscy robią to samo . Opcje wyższego poziomu, takie jak UIViews, opierają się na opcjach niższego poziomu, takich jak CoreGraphics i OpenGL, po prostu są zawinięte w kod, aby ułatwić Ci korzystanie z nich.

Nie używaj CoreGraphics, jeśli zamierzasz po prostu skończyć na ponownym pisaniu UIView. Możesz jednak zyskać trochę szybkości dzięki CoreGraphics, o ile wykonasz wszystkie swoje rysunki w jednym widoku, ale czy naprawdę jest to warte? Odpowiedź, którą znalazłem, to zazwyczaj nie. Kiedy zaczynałem grę, pracował z iPhone 3G. jak moja gra wzrosła w złożoności, zacząłem widzieć pewne opóźnienia, ale z nowszymi urządzeniami było całkowicie niezauważalne. Teraz mam mnóstwo akcji dzieje, a jedynym lag wydaje się być spadek w 1-3 fps podczas gry w najbardziej skomplikowanym poziomie na iPhone 4.

Mimo to postanowiłem użyć instrumentów, aby znaleźć funkcje, które zajmowały najwięcej czasu. Okazało się, że problemy nie były związane z używaniem UIViews . Zamiast tego wielokrotnie wywołanie CGRectMake dla pewnych obliczeń wykrywania kolizji i osobnego ładowania obrazów i plików audio dla pewnych klas, które używają tych samych obrazów, zamiast pobierania ich z jednej centralnej klasy pamięci masowej.

Tak więc w końcu możesz osiągnąć niewielki zysk z używania CoreGraphics, ale zazwyczaj nie będzie to tego warte lub może nie mieć żadnego efektu. Używam CoreGraphics tylko wtedy, gdy rysuję geometryczne kształty, a nie tekst i obrazy.

 6
Author: WolfLink,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-02 07:52:06