
Ada banyak tips dan trik yang memungkinkan pengembang iOS tahu bagaimana membuat optimasi kinerja agar animasi dalam aplikasi berjalan dengan lancar. Setelah membaca artikel Anda akan menyadari apa artinya 16,67 milidetik untuk pengembang iOS, dan alat mana yang lebih baik untuk digunakan untuk melacak kode.
Artikel ini didasarkan pada ceramah utama yang disampaikan oleh
Luke Parham , saat ini seorang insinyur iOS di Apple dan penulis tutorial untuk pengembangan iOS di RayWenderlich.com, di Konferensi Pengembang Seluler Internasional
MBLT DEV 2017 .
"Hai teman-teman. Jika Anda bisa, katakanlah, Anda dapat mencukur 10 detik dari waktu boot, kalikan dengan 5 juta pengguna dan itu 50 juta detik setiap hari. Lebih dari satu tahun, itu mungkin puluhan masa hidup. Jadi jika Anda membuatnya boot sepuluh detik lebih cepat, Anda telah menyelamatkan selusin nyawa. Itu benar-benar layak, bukan begitu? β
Steve Jobs tentang kinerja (waktu boot Apple II).Performa di iOS atau cara keluar dari main
Utas utama bertanggung jawab untuk menerima input pengguna dan menampilkan hasil ke layar. Menerima ketukan, wajan, semua gerakan dan kemudian rendering. Sebagian besar ponsel modern menghasilkan 60 frame per detik. Ini berarti bahwa semua orang ingin melakukan semua pekerjaan dalam 16,67 milidetik. Jadi, keluar dari utas utama adalah hal yang sangat besar.
Jika sesuatu membutuhkan waktu lebih lama dari 16,67 milidetik, maka Anda akan secara otomatis menjatuhkan bingkai, dan pengguna Anda akan melihatnya ketika ada animasi. Beberapa perangkat bahkan memiliki lebih sedikit waktu untuk melakukan, misalnya, iPad baru memiliki 120 Hertz, sehingga hanya ada 8 milidetik per frame untuk melakukan pekerjaan.
Frame yang terjatuh
Aturan # 1: gunakan CADisplayLink untuk melacak frame yang terjatuh
CADisplayLink adalah timer khusus yang menyala pada Vsync. Vsync adalah ketika aplikasi menampilkan layar, dan itu terjadi setiap 16 milidetik. Untuk tujuan pengujian, di AppDelegate Anda, Anda dapat mengatur CADisplayLink ditambahkan ke loop run utama dan kemudian hanya memiliki fungsi lain di mana Anda melakukan sedikit matematika. Kemudian Anda melacak berapa lama aplikasi telah berjalan dan sudah berapa lama sejak terakhir kali fungsi ini dipecat. Dan lihat apakah butuh lebih dari 16 milidetik.

Ini hanya menyala ketika benar-benar bisa di-render. Jika Anda melakukan banyak pekerjaan dan Anda memperlambat utas utama, ini akan berjalan 100 milidetik kemudian, yang berarti Anda telah melakukan terlalu banyak pekerjaan dan Anda telah menjatuhkan bingkai pada waktu itu.
Sebagai contoh, ini adalah Catstagram aplikasi. Memiliki gagap ketika gambar sedang dimuat. Dan kemudian Anda dapat melihat bahwa bingkai dijatuhkan pada waktu tertentu dan memiliki waktu yang berlalu seperti 200 milidetik. Itu berarti aplikasi ini melakukan sesuatu yang terlalu lama.

Pengguna tidak menyukai pengalaman seperti itu terutama jika aplikasi tersebut mendukung perangkat yang lebih tua seperti iPhone 5, iPod lama, dll.
Profiler waktu
Time Profiler mungkin adalah alat yang paling berguna untuk melacak barang-barang tersebut. Alat-alat lain berguna tetapi, pada akhirnya, di Fyusion kami menggunakan Time Profiler seperti 90% dari waktu. Tersangka aplikasi yang biasa adalah scrollview, teks, dan gambar.
Gambar adalah gambar yang sangat besar. Kami memiliki decoding JPEG - "UIImageView" sama dengan beberapa UIImage. UIimages mendekode semua JPEG untuk aplikasi. Mereka melakukannya perlahan sehingga Anda tidak dapat benar-benar melacak kinerja secara langsung. Itu tidak terjadi tepat ketika Anda mengatur gambar tetapi Anda dapat melihatnya dalam jejak profiler waktu.
Pengukuran teks adalah hal besar lainnya. Itu muncul, misalnya jika Anda memiliki banyak yang sangat kompleks seperti Jepang atau Cina. Ini bisa memakan waktu lama untuk melakukan pengukuran garis.
Tata letak hierarki juga memperlambat rendering aplikasi. Ini terutama berlaku dengan Tata Letak Otomatis. Ini nyaman tetapi juga lambat secara agresif dibandingkan dengan melakukan tata letak manual. Jadi itu adalah salah satu dari kompromi tersebut. Jika itu memperlambat aplikasi, mungkin sudah saatnya untuk beralih dari itu dan mencoba beberapa teknik tata letak lainnya.
Contoh jejak

Pada pohon panggilan contoh, Anda dapat melihat seberapa banyak pekerjaan CPU Anda lakukan. Anda dapat mengubah tampilan, melihatnya dengan utas, melihatnya dengan CPU. Biasanya, hal yang paling menarik adalah memisahkan dengan utas dan kemudian melihat apa yang ada di utama.
Banyak kali ketika Anda pertama kali mulai melihat ini, tampaknya sangat luar biasa. Anda kadang-kadang memiliki perasaan: "Apa semua sampah ini? Saya tidak tahu apa artinya ini "FRunLoopDoSource0".
Tetapi itu adalah salah satu hal di mana Anda dapat menggali dan memahami bagaimana berbagai hal bekerja dan itu mulai masuk akal. Jadi Anda dapat mengikuti jejak stack dan melihat semua hal sistem yang tidak Anda tulis. Tetapi di bagian bawah, Anda dapat melihat kode Anda yang sebenarnya.
Pohon panggilan
Sebagai contoh, kami memiliki aplikasi yang sangat sederhana yang memiliki fungsi utama, dan kemudian memanggil beberapa metode di dalamnya. Apa yang dilakukan profiler waktu adalah mengambil snapshot dari apa pun jejak tumpukan Anda sekarang secara default setiap milidetik. Kemudian ia menunggu satu milidetik dan mengambil snapshot, di mana Anda telah memanggil "main" yang disebut "foo" yang disebut "bar". Ada jejak tumpukan pertama di atas tangkapan layar. Sehingga dikumpulkan. Kami memiliki hitungan ini: 1, 1, 1.

Masing-masing fungsi ini disebut satu kali. Kemudian milidetik kemudian kami menangkap tumpukan lain. Dan kali ini, itu adalah hal yang persis sama, kita menghitung semua dengan 2.

Kemudian pada milidetik ketiga, kita memiliki tumpukan panggilan yang sedikit berbeda. Main memanggil "bar" secara langsung. Utama dan bar naik satu per satu. Tapi kemudian kita harus berpisah. Terkadang panggilan utama βfooβ, terkadang panggilan utama βbarβ secara langsung. Itu terjadi satu kali. Satu metode telah dipanggil di dalam yang lain.
Lebih jauh, satu metode telah dipanggil di dalam yang lain yang memanggil metode ketiga. Kita melihat bahwa "buz" dipanggil dua kali. Tetapi ini adalah metode kecil yang terjadi antara satu milidetik.
Menggunakan waktu profiler, penting untuk diingat bahwa itu tidak memberikan waktu yang tepat. Tidak tahu persis berapa lama suatu metode. Ini memberitahu seberapa sering muncul dalam snapshot, yang hanya bisa memperkirakan berapa lama eksekusi dari setiap metode. Karena jika ada sesuatu yang cukup pendek, itu tidak akan pernah muncul.

Jika Anda beralih ke mode konsol di pohon panggilan, Anda dapat melihat semua peristiwa penurunan frame dan Anda dapat mencocokkannya. Kami memiliki banyak bingkai yang terjatuh dan kami memiliki banyak pekerjaan yang terjadi. Anda dapat memperbesar dalam profiler waktu dan melihat apa yang sedang dieksekusi hanya di bagian ini.

Sebenarnya, di Mac, secara umum, Anda bisa mengklik opsi pada segitiga pengungkapan dan itu secara ajaib akan terbuka dan menunjukkan kepada Anda apa pun yang paling penting di sana. Ini akan jatuh ke apa pun yang melakukan sebagian besar pekerjaan. Dan 90% dari waktu itu akan menjadi CFRunLoopRun, dan kemudian callback.

Seluruh aplikasi didasarkan pada Run Loop. Anda memiliki loop ini yang berlangsung selamanya dan kemudian pada setiap iterasi dari loop, callback dipanggil. Ketika Anda sampai pada titik ini, Anda dapat menelusuri masing-masing dan pada dasarnya melihat apa yang menjadi tiga atau empat hambatan utama Anda.
Jika kita menelusuri salah satu dari ini, kita dapat melihat hal-hal seperti itu di mana sangat mudah untuk melihatnya, dan menjadi seperti: "Wow, saya tidak tahu apa yang sedang dilakukan." Seperti renders, penyedia gambar, IO.

Ada opsi untuk menyembunyikan pustaka sistem. Sangat menggoda untuk bersembunyi, tetapi pada kenyataannya, ini sebenarnya adalah hambatan terbesar dalam aplikasi.
Ada bobot yang menunjukkan persentase pekerjaan yang dilakukan fungsi atau metode tertentu ini. Dan jika kita menelusuri contohnya, kita memiliki 34% dan itu terjadi karena Apple jpeg_decode_image_all. Setelah sedikit riset, menjadi jelas bahwa itu berarti bahwa decoding JPEG terjadi pada utas utama dan menyebabkan sebagian besar frame turun.

Aturan # 2
Secara umum, lebih baik untuk memecahkan kode JPEG di latar belakang. Sebagian besar perpustakaan pihak ketiga (AsyncDisplayKit, SDWebImage, ...) melakukan ini di luar kotak. Jika Anda tidak ingin menggunakan kerangka kerja, Anda bisa melakukannya sendiri. Apa yang Anda lakukan adalah Anda memasukkan gambar, dalam hal ini, itu adalah perluasan dari UIImage, dan kemudian Anda mengatur konteks dan Anda menggambar gambar secara manual ke dalam konteks ke dalam CGBitmap.

Ketika Anda melakukannya, Anda dapat memanggil metode Decoded Image () dari utas latar belakang. Itu akan selalu mengembalikan gambar yang diterjemahkan. Tidak ada cara untuk memeriksa apakah UIImage tertentu sudah diterjemahkan, dan Anda harus selalu melewati mereka di sini. Tetapi jika Anda melakukan cache dengan benar, itu tidak melakukan pekerjaan ekstra.
Melakukan ini secara teknis kurang efisien. Menggunakan UIimageView sangat dioptimalkan, sangat efisien. Ini akan melakukan decoding perangkat keras sehingga merupakan trade-off. Gambar Anda akan diterjemahkan lebih lambat dengan cara ini. Tetapi hal baiknya adalah Anda dapat mengirim ke antrian latar belakang, mendekode gambar Anda dengan metode yang baru saja kita lihat, dan kemudian melompat kembali ke utas utama dan mengatur konten Anda.

Meskipun pekerjaan itu memakan waktu lebih lama, mungkin itu tidak terjadi pada utas utama, jadi itu tidak memblokir interaksi pengguna karena tidak memblokir gulir. Jadi itu adalah kemenangan.
Peringatan memori
Tanda apa pun yang Anda dapatkan peringatan memori Anda ingin meninggalkan semuanya, hapus semua memori yang tidak terpakai yang Anda bisa. Tetapi jika Anda memiliki hal-hal yang terjadi pada utas latar belakang, mengalokasikan JPEG yang didekodekan besar ini membutuhkan banyak memori baru pada utas latar belakang.
Ini terjadi di aplikasi Fyuse. Jika saya akan melompat ke utas latar belakang, men-decode semua JPEG saya, dalam beberapa kasus seperti telepon yang lebih tua, sistem akan membunuhnya secara instan. Dan itu karena mengirimkan peringatan memori yang mengatakan seperti: "Hei! Singkirkan memori Anda βtetapi antrian latar tidak mendengarkan. Apa yang terjadi jika Anda mengalokasikan semua gambar ini dan kemudian crash setiap waktu. Cara mengatasi ini adalah dengan ping utas utama dari utas latar belakang.

Secara umum, utas utama adalah antrian. Banyak hal menjadi antri dan terjadi di utas utama. Ketika Anda pergi ke latar belakang di Objective-C, Anda dapat menggunakan performSelectorOnMainThread: withObject: waitUntilDone:. Ini akan meletakkannya di akhir baris antrian utama sehingga jika antrian utama sedang sibuk memproses peringatan memori, panggilan fungsi ini akan pergi ke akhir baris dan menunggu semua peringatan memori diproses sebelum melakukan semua alokasi memori
Di Swift, ini lebih sederhana. Anda dapat melakukan pengiriman blok kosong utama secara serempak di utama.
Berikut adalah contoh di mana kami telah membereskan dan kami sedang melakukan decoding gambar pada antrian latar belakang. Dan menggulir secara visual jauh lebih cantik. Kami masih memiliki penurunan bingkai tetapi ini pada iPod 5g, jadi ini adalah salah satu hal terburuk yang dapat Anda uji pada yang masih mendukung seperti iOS 10 dan 11.

Ketika Anda memiliki bingkai tetes ini, Anda bisa terus mencari. Masih ada pekerjaan yang terjadi dan menyebabkan penurunan bingkai ini. Ada banyak hal yang dapat Anda lakukan untuk membuatnya lebih cepat.
Singkatnya, itu tidak selalu mudah, tetapi jika Anda memiliki hal-hal kecil yang menghabiskan banyak waktu, Anda dapat melakukannya di latar belakang.
Pastikan itu tidak terkait dengan UIKit. Banyak kelas UIKit yang tidak aman untuk thread dan Anda tidak dapat mengalokasikan UIView di latar belakang.
Gunakan Core Graphics jika Anda perlu melakukan hal-hal gambar di latar belakang. Jangan sembunyikan pustaka sistem. Dan jangan lupa tentang peringatan memori.
Ini adalah bagian pertama dari sebuah artikel berdasarkan presentasi Luke Parham. Jika Anda ingin mempelajari lebih lanjut tentang cara kerja UI di iOS, mengapa harus menggunakan jalur bezier dan kapan harus kembali ke manajemen memori manual, baca bagian kedua dari artikel di
sini .
Video
Tonton pembicaraan lengkap di sini: