
Ada berbagai trik dan trik yang membantu mengoptimalkan kerja aplikasi iOS, ketika satu tugas harus diselesaikan dalam 16,67 milidetik. Kami memberi tahu Anda cara membongkar utas utama dan alat apa yang lebih cocok untuk melacak tumpukan panggilan di dalamnya.
“Guys, mari kita bayangkan bahwa Anda dapat mengurangi waktu startup hingga 10 detik. Mengalikan ini dengan 5 juta pengguna, kita akan memiliki 50 juta detik setiap hari. Dalam setahun ini akan berjumlah sekitar sepuluh kehidupan manusia. Karena itu, jika Anda membuat unduhan awal 10 detik lebih cepat, Anda akan menghemat beberapa puluh nyawa. Benar-benar layak, bukan? ”
Steve Jobs pada kinerja (waktu startup komputer Apple II).
Artikel ini didasarkan pada laporan oleh pengembang iOS Fyusion Luc Parham, yang berbicara di MBLT DEV International Mobile Developers Conference tahun lalu.
MBLT DEV 2018 akan diadakan di Moskow pada 28 September. Tiket adalah yang termurah saat ini. Menurut tradisi, sementara Komite Program memilih laporan, Anda dapat membeli tiket early bird untuk konf. Ambil kesempatan ini sekarang. Mulai 29 Juni, tiket akan lebih mahal.
Kerugian bingkai
Utas utama mengeksekusi kode yang bertanggung jawab untuk peristiwa jenis sentuh dan bekerja dengan UI. Dia membuat layar. Sebagian besar smartphone modern menghasilkan 60 frame per detik. Ini berarti bahwa tugas harus diselesaikan dalam 16,67 milidetik (1000 milidetik / 60 frame). Karena itu, akselerasi di Main Thread penting.
Jika beberapa operasi membutuhkan lebih dari 16,67 milidetik, frame loss terjadi secara otomatis, dan pengguna aplikasi akan memperhatikan ini ketika memainkan animasi. Pada beberapa perangkat, rendering bahkan lebih cepat, misalnya, pada iPad Pro 2017, kecepatan refresh layar adalah 120 Hz, sehingga hanya ada 8 milidetik untuk menyelesaikan operasi dalam satu frame.
Aturan # 1
CADisplayLink
adalah penghitung waktu khusus yang dimulai selama sinkronisasi vertikal (Vsync). Sinkronisasi vertikal memastikan bahwa tidak lebih dari 16,67 milidetik dialokasikan untuk merender bingkai. Sebagai tanda centang di AppDelegate, Anda dapat mendaftarkan CADisplayLink
di loop run utama, dan kemudian Anda akan memiliki fungsi tambahan yang akan melakukan perhitungan. Anda dapat melacak durasi aplikasi dan mencari tahu berapa banyak waktu telah berlalu sejak peluncuran terakhir fungsi ini.
.
Permulaan terjadi ketika kebutuhan untuk rendering muncul. Jika banyak operasi berbeda dilakukan yang membebani utas utama, maka fungsi ini dimulai dengan penundaan 100 milidetik. Ini berarti bahwa terlalu banyak pekerjaan yang dilakukan, dan pada saat itu ada kehilangan personel.
Ini adalah aplikasi Catstagram. Saat mengunduh gambar, aplikasi mulai melambat. Kami melihat bahwa laju bingkai menurun pada titik tertentu, dan waktu pemuatan berlangsung sekitar 200 milidetik. Sepertinya ada sesuatu yang menghabiskan terlalu banyak waktu.
.
Pengguna tidak akan senang dengan ini, terutama jika aplikasi berjalan pada perangkat yang lebih tua, seperti iPhone 5 atau model iPod yang lebih tua, dll.
Profiler waktu
Alat yang berguna untuk melacak masalah tersebut adalah Time Profiler. Alat lain juga berguna, tetapi pada akhirnya di Fyusion 90% dari waktu kami menggunakan Time Profiler. Biasanya, masalah dalam aplikasi terkait dengan ScrollView, area dengan teks dan gambar.
Gambar itu penting. Kami mendekode format JPEG menggunakan UIImage
. Mereka melakukannya dengan lambat, dan kami tidak dapat melacak kinerja mereka secara langsung. Ini tidak terjadi segera setelah mengatur gambar di UIImageView
, tetapi Anda dapat melihat momen ini melalui pelacakan di Time Profiler.
Pemformatan teks adalah poin penting lainnya. Itu penting ketika aplikasi memiliki sejumlah besar teks "kompleks", misalnya, dalam bahasa Jepang atau Cina. Mungkin perlu waktu lama untuk menghitung ukuran yang benar untuk baris dengan teks.
Markup antarmuka juga memperlambat rendering dalam aplikasi. Ini terutama berlaku untuk alat AutoLayout. AutoLayout nyaman digunakan, tetapi sangat memperlambat aplikasi dibandingkan dengan markup manual. Kita harus membuat konsesi. Jika AutoLayout memperlambat aplikasi, mungkin sudah saatnya untuk mengabaikannya dan mencoba jenis markup lainnya.
Pola jejak

Dalam pohon contoh panggilan ini, Anda dapat melihat jenis pekerjaan apa yang dilakukan CPU. Anda dapat mengubah jenis jejak, melihatnya dari sudut pandang utas, prosesor. Biasanya hal yang paling menarik adalah membagi jejak menjadi utas dan memonitor utas.
Analisis jejak awal mungkin tampak rumit. Tidak selalu mungkin untuk segera mengetahui apa arti FRunLoopDoSource0
.
Mengaduk-aduk jejak, Anda dapat memahami bagaimana sistem bekerja, dan kemudian semuanya masuk akal. Anda dapat melacak jejak tumpukan dan melihat semua elemen sistem yang tidak Anda tulis. Tetapi di bagian paling bawah adalah kode sumber Anda.
Panggil pohon
Misalkan kita memiliki aplikasi yang sangat sederhana. Ini berisi fungsi utama yang memanggil beberapa fungsi lainnya. Inti dari karya Time Profiler adalah ia mengambil snapshot dari kondisi jejak stack saat ini dengan frekuensi satu milidetik (secara default). Setelah satu milidetik lagi, ia mengambil snapshot dari jejak itu. Ini memanggil fungsi utama, yang memanggil fungsi " foo
", yang disebut fungsi " bar
". Jejak tumpukan awal ditunjukkan pada tangkapan layar di bawah ini. Data-data ini dikumpulkan bersama. Berlawanan dengan masing-masing fungsi, angka ditunjukkan: 1, 1, 1.

Ini berarti bahwa masing-masing fungsi ini dipanggil satu kali. Kemudian, setelah satu milidetik, kita mendapatkan suntikan tumpukan lagi. Kali ini tampilannya persis sama, jadi semua angka bertambah 1, dan kita mendapat 2, 2, 2.

Selama milidetik ketiga, tumpukan panggilan kami terlihat sedikit berbeda. Fungsi utama memanggil bar
secara langsung. Oleh karena itu, satu unit lagi ditambahkan ke fungsi utama dan fungsi " bar
", dan nilainya menjadi 3. Selanjutnya, pemisahan terjadi. Terkadang fungsi utama memanggil " foo
" secara langsung, kadang-kadang " bar
" dipanggil langsung. Ini pernah terjadi sekali. Satu fungsi dipanggil melalui yang lain.
Selanjutnya, satu fungsi disebut yang lain, yang disebut fungsi ketiga. Kita melihat bahwa fungsi " baz
" dipanggil dua kali. Tetapi fungsi ini sangat tidak penting sehingga disebut lebih cepat dari satu milidetik.
Saat menggunakan Time Profiler, penting untuk diingat bahwa itu tidak menunjukkan interval waktu tertentu. Itu tidak menampilkan waktu eksekusi yang tepat dari suatu fungsi. Dia hanya melaporkan seberapa sering itu muncul dalam gambar, yang hanya memberikan nilai perkiraan durasi setiap fungsi. Karena beberapa proses cukup cepat, mereka tidak pernah ditampilkan pada gambar.

Saat mengalihkan panggilan ke mode konsol, Anda dapat melihat dan membandingkan semua momen penurunan frame rate. Dalam contohnya, kehilangan bingkai terjadi beberapa kali dan berbagai proses dilakukan.

Mengklik alt-klik pada macOS akan memperluas bagian dan sub-bagian, bukan hanya yang dipilih. Mereka akan diurutkan berdasarkan jumlah pekerjaan yang dilakukan. Dalam 90% kasus, CFRunLoopRun
, diikuti oleh panggilan balik.
Aplikasi ini didasarkan sepenuhnya pada satu siklus pelaksanaan tugas Run Loop. Ada siklus berulang tanpa akhir, dan pada setiap iterasi, callback diluncurkan. Jika Anda melihat callback ini, Anda dapat menyorot kemacetan teratas.
Setelah melihat tantangan-tantangan ini secara lebih rinci, kemungkinan besar Anda tidak akan mengerti apa yang mereka lakukan. Ini dapat berupa renders, penyedia gambar, IO.

Ada opsi yang memungkinkan Anda untuk menyembunyikan pustaka sistem. Mereka sebenarnya adalah area masalah aplikasi.
Ada meter yang dalam persentase menunjukkan seberapa banyak pekerjaan yang dilakukan fungsi atau operasi tertentu. Jika kita melihat contoh ini, kita akan melihat nilainya - 34%. Ini adalah proses Apple jpeg_decode_image_all
. Setelah mempelajarinya menjadi jelas bahwa decoding gambar JPEG terjadi di utas utama, dan dalam kebanyakan kasus ini adalah penyebab hilangnya bingkai.

Aturan # 2
Secara umum, decoding gambar jpeg harus dilakukan di latar belakang. Sebagian besar pustaka pihak ketiga (AsyncDisplayKit, SDWebImage, dll.) Dapat melakukan ini secara default. Jika Anda tidak ingin menggunakan framework, Anda bisa melakukan decoding secara manual. Untuk melakukan ini, Anda dapat menulis ekstensi di atas UIImage
di mana Anda membuat konteks dan menggambar secara manual.

Saat melakukan operasi ini, Anda dapat memanggil fungsi decodeImage
bukan dari utas utama. Itu akan selalu mengembalikan gambar yang diterjemahkan. Tidak ada cara untuk memeriksa apakah gambar UIImage tertentu telah melewati pengodean ulang, jadi Anda harus selalu melewati mereka melalui metode ini. Tetapi jika Anda menyimpan data dengan benar, tidak akan ada proses yang tidak perlu dalam sistem.
Dari sudut pandang teknis, ini kurang efektif. Menggunakan kelas UIImageView
tampaknya dioptimalkan dan efisien. Tetapi juga melakukan decoding perangkat keras, sehingga juga memiliki kekurangannya. Dengan metode ini, gambar Anda akan diterjemahkan lebih lambat. Tetapi ada kabar baik - Anda dapat mendekode gambar dengan cara di atas, bukan pada utas utama, dan kemudian kembali ke utas utama dan mengonfigurasi antarmuka.

Terlepas dari kenyataan bahwa operasi ini membutuhkan lebih banyak waktu, itu mungkin tidak dilakukan pada utas utama, yang berarti tidak mengganggu aktivitas pengguna dalam aplikasi, karena itu tidak memperlambat pengguliran rekaman. Solusi yang menguntungkan.
Peringatan kehabisan memori
Dengan sinyal memori rendah, saya ingin menghapus semua data yang tidak digunakan yang mungkin. Tetapi jika berbagai proses dilakukan pada aliran pihak ketiga, maka penempatan gambar JPEG yang didekodekan volumetrik pada mereka akan memakan sebagian besar ruang kosong.
Masalah seperti itu terjadi pada aplikasi Fyuse. Jika saya telah mendekodekan semua gambar JPEG saya pada aliran pihak ketiga, maka dalam beberapa kasus, misalnya, pada model ponsel lama, sistem ini akan langsung memecah aplikasi. Ini karena fakta bahwa aliran tugas pihak ketiga tidak menanggapi peringatan tentang memori yang tidak mencukupi dari sistem, seperti "Hei, hapus data yang tidak perlu!". Situasi berikut terjadi: pertama Anda menempatkan semua gambar ini pada aliran pihak ketiga, dan kemudian aplikasi terus-menerus macet. Jika utas pihak ketiga mengirim sinyal ke utas utama tentang apa yang terjadi dalam sistem, maka masalah seperti itu tidak akan terjadi.
Bekerja tanpa kegagalan

Intinya, utas utama adalah antrian yang terdiri dari proses. Saat bekerja dengan utas pihak ketiga, Anda dapat menulis perintah performSelectorOnMainThread:withObject:waitUntilDone:
di Objective-C. Berkat dia, tugas akan diletakkan di akhir antrian di utas utama. Karena itu, jika utas utama sibuk memproses pemberitahuan kehabisan memori, memanggil perintah ini akan memungkinkan Anda untuk menunggu sampai semua pemberitahuan diproses, dan baru kemudian memulai proses rumit memuat dan menempatkan data. Di Swift, ini terlihat sedikit lebih sederhana. DispatchQueue.main.sync
membebaskan ruang pada utas utama.
Ini adalah contoh lain. Kami telah membebaskan memori dan mendekode gambar pada aliran pihak ketiga. Menggulir rekaman secara visual menjadi lebih baik. Kami masih kehilangan bingkai karena fakta bahwa kami sedang menguji iPod 5g. Ini adalah salah satu model pengujian terburuk yang masih mendukung versi iOS 10 dan 11.

Jika Anda mengalami kehilangan bingkai seperti ini, Anda masih dapat melihat rekaman itu. Namun, masih ada proses yang terus membuat personil kehilangan. Ada cara lain untuk membuat aplikasi berjalan lebih cepat.
Tentu saja, tidak selalu mudah untuk mengoptimalkan aplikasi. Tetapi jika Anda memiliki tugas yang membutuhkan waktu relatif lama untuk diselesaikan, Anda harus meletakkannya di utas latar belakang. Pastikan bahwa tugas-tugas ini tidak terkait dengan UI, karena banyak kelas UIKit tidak aman, yaitu, Anda tidak dapat membuatnya di backend.
Gunakan Core Graphics jika Anda perlu memproses gambar pada aliran pihak ketiga. Jangan menyembunyikan tampilan pustaka sistem. Ingat peringatan kehabisan memori.
Selamat datang di MBLT DEV 2018
Datang tanggal 28 September hingga Konferensi Pengembang Seluler MBLT DEV 2018 Internasional ke-5 di Moskow. Pembicara pertama sudah ada di situs, dan early bird terbaru masih dijual. Harga tiket akan naik pada tanggal 29 Juni. Beli tiket sekarang dengan harga terendah.

Baca tentang implementasi antarmuka pengguna di iOS, penggunaan kurva Bezier dan alat lain yang bermanfaat di bagian kedua artikel, yang akan kami terbitkan pada 28 Juni.