Dasar-Dasar Matematika dari Tata Letak Otomatis

Banyak pengembang percaya bahwa Tata Letak Otomatis adalah rem dan masalah, dan sangat sulit untuk men-debug itu. Dan bagus jika kesimpulan ini dibuat berdasarkan pengalaman saya sendiri, dan kadang-kadang hanya "Saya dengar, saya bahkan tidak akan mencoba berteman dengannya".

Tapi mungkin alasannya bukan di luar, tetapi di dalam. Misalnya, burung yang paling berbahaya di dunia kasuari tidak akan menyerang orang tanpa alasan, hanya untuk membela diri. Karenanya, coba asumsikan sesaat bahwa ini bukan Tata Letak Otomatis yang buruk, dan Anda tidak cukup memahaminya dan tidak tahu cara memasak. Inilah yang dilakukan Anton Sergeyev dan menggali teori untuk memahami segala sesuatu dengan tepat. Kami ditawari pemerasan siap pakai tentang dasar matematika dari Tata Letak Otomatis.




Tata Letak Otomatis adalah sistem tata letak . Sebelum membahasnya, mari kita bicara tentang pengaturan huruf modern secara umum. Lalu mari kita berurusan dengan Tata Letak Otomatis - kita akan mencari tahu tugas apa yang dipecahkan dan bagaimana melakukannya. Pertimbangkan fitur - fitur dalam penerapan Tata Letak Otomatis di iOS , dan coba kembangkan tip-tip praktis yang dapat membantu Anda mengatasinya.

Cerita ini akan sangat dekat dengan artikel matematika, jadi pertama-tama kita sepakat tentang notasi untuk berbicara dalam bahasa yang sama.


Tentang pembicara: Anton Sergeev ( antonsergeev88 ) bekerja di tim Yandex.Mart, berurusan dengan klien seluler untuk Maps di iOS. Sebelum pengembangan ponsel, ia berurusan dengan sistem kontrol pembangkit listrik, di mana biaya kesalahan dalam kode terlalu tinggi untuk ditoleransi.

Penunjukan


Sistem persamaan linear sudah biasa bagi kita sejak sekolah - mereka ditunjukkan oleh kurung kurawal, dan solusinya sudah tanpa. Juga, sistem persamaan linear memiliki entitas yang beroperasi dengan Tata Letak Otomatis - pembatasan. Mereka ditunjukkan oleh garis lurus.



Yang aneh dan, seperti yang sudah kita ketahui, burung berbahaya tidak sengaja dicat di sudut atas slide. Untuk menghormati kasuari (lat. Kasuari), yang, tentu saja, tinggal di Australia, sebuah algoritma diberi nama di semua iPhone kami.

Tata Letak Otomatis memiliki batasannya sendiri, kami akan menunjukkannya dengan warna sesuai urutan prioritas: merah - wajib; kuning - tinggi; biru - rendah.

Tata letak


Ketika saya sedang membuat presentasi, saya meletakkan berbagai elemen di layar, misalnya, kasuari. Untuk melakukan ini, saya memutuskan bahwa kasuari adalah gambar persegi panjang. Anda perlu mengaturnya pada selembar yang memiliki sumbu dan sistem koordinatnya sendiri, dan untuk ini saya menentukan koordinat sudut kiri atas, lebar dan tinggi.



Mengetahui keempat nilai ini cukup untuk mewakili Tampilan apa pun.

Algoritma No. 1


Saat menempatkan kasuari di atas kertas, kami dengan sederhana menggambarkan algoritma tata letak pertama:

  • tentukan koordinat dan ukuran;
  • terapkan ke UIView.

Algoritma berfungsi, tetapi cukup sulit untuk digunakan, jadi kami akan menyederhanakannya lebih lanjut.

Misalkan di bawah ini adalah solusi untuk beberapa sistem persamaan linear.



Sistem persamaan linear adalah istimewa karena banyak operasi didefinisikan di atasnya: garis lipat, mengalikannya dengan konstanta, dll. Operasi-operasi ini disebut transformasi linear, dan dengan bantuan mereka sistem dikurangi menjadi bentuk sewenang-wenang.

Keindahan dari transformasi linear adalah mereka dapat dibalik. Ini membawa kita ke ide yang menarik dan agak halus yang dengannya seluruh tata letak modern dimulai.

Biarkan ada View - persegi panjang dengan koordinat dan ukurannya. Kami ingin mengaturnya sehingga pusat bertepatan dengan poin yang diberikan. Kami memodelkan pusat menggunakan transformasi linear - koordinat sudut kiri atas + setengah lebar .



Kami memodelkan pusat dengan transformasi linear, bukan: hanya ada koordinat titik kiri atas, lebar dan tinggi.

Demikian pula, Anda dapat mensimulasikan lekukan lain, misalnya, 20 poin dari sudut kanan.

Ini adalah ide transformasi linear yang memungkinkan kita untuk membuat berbagai sistem penyusunan huruf.

Pertimbangkan contoh sederhana. Kami menulis sebuah sistem yang kami gunakan dengan koordinat tengah dan kanan, lebar dan hubungan antara lebar dan tinggi. Kami memecahkan sistem dan mendapatkan jawabannya.



Jadi kita sampai pada algoritma kedua.

Algoritma No. 2


Iterasi kedua algoritma terdiri dari item berikut:

  • membuat sistem persamaan linear;
  • kita menyelesaikannya;
  • terapkan solusi untuk UIView.

Bayangkan bahwa kita berada di abad kedua puluh, pada saat peralatan komputer masih dalam masa pertumbuhan, dan kita adalah orang pertama yang menciptakan sistem tata letak kita sendiri. Diciptakan, dikemas, diberikan kepada pengguna, dan ia mulai menggunakannya - mengisi parameter awal dan mentransfernya ke sistem kami.



Ada masalah - sistem ini tidak memiliki solusi tunggal. Masalahnya tidak luar biasa, benar-benar semua sistem tata letak berjalan ke dalamnya, dan disebut kurangnya solusi .

Tidak banyak jalan keluar dari situasi ini:

  • Anda bisa jatuh - ini adalah metode yang sangat umum. Mereka yang bekerja dengan MacOS tahu bahwa NSLayoutConstraintManager melakukan hal itu.
  • Kembalikan nilai default . Dalam konteks tata letak, kami selalu dapat mengembalikan semua nol.
  • Cara yang lebih terkenal dan rumit adalah untuk mencegah input yang salah . Metode ini digunakan oleh sistem tata letak yang populer, misalnya, Yoga , yang dikenal sebagai Layout Flex . Sistem semacam itu mencoba membuat antarmuka yang tidak memungkinkan input yang salah.
  • Ada cara lain untuk menyelesaikan semua masalah - untuk memikirkan kembali semuanya dari awal dan awalnya untuk mencegah terjadinya masalah ini . Tata Letak Otomatis berjalan seperti itu.

Tata Letak Otomatis Pernyataan dan solusi masalah


Kami memiliki gambar persegi panjang dan untuk mengidentifikasinya secara unik, kami membutuhkan 4 parameter:

  • koordinat sudut kiri atas;
  • lebar dan tinggi.



Tata Letak Otomatis sangat bertele-tele. Dibandingkan dengan sistem persamaan linear, jauh lebih sulit untuk menempatkan segala sesuatu di layar dengannya. Karena itu, kami akan mempertimbangkan, tanpa kehilangan keumuman, kasus satu dimensi.



Semuanya sangat sederhana: ruang adalah garis lurus, dan semua benda yang dapat ditempatkan di dalamnya adalah titik pada garis lurus. Satu nilai: X = X P sudah cukup untuk menentukan posisi titik.

Pertimbangkan pendekatan Tata Letak Otomatis. Ada ruang di mana pembatasan ditetapkan. Solusi yang ingin kita dapatkan adalah X = X 0 , dan tidak ada yang lain.

Ada masalah - kami belum mendefinisikan operasi dengan pembatasan. Kami tidak dapat secara langsung menyimpulkan dari catatan bahwa X = X 0 , kami tidak dapat melipatgandakan apa pun atau menambahkan sesuatu ke apa pun. Untuk melakukan ini, kita perlu mengubah batasan menjadi apa yang bisa kita kerjakan - menjadi sistem persamaan dan ketidaksetaraan.



Tata Letak Otomatis mengubah sistem persamaan dan ketidaksetaraan sebagai berikut.

  • Pertama memperkenalkan 2 variabel tambahan yang tidak negatif dan saling bergantung . Setidaknya satu dari mereka sama dengan nol.
  • Batasan itu sendiri dikonversi ke notasi X = X 0 + a + - a - .

Poin X 0   - solusi sistem: jika + dan a - sama dengan nol, maka ini akan menjadi benar. Tetapi poin lain pada baris ini akan menjadi solusi.

Oleh karena itu, perlu untuk menemukan yang terbaik di antara banyak solusi. Untuk melakukan ini, kami memperkenalkan fungsional - fungsi biasa yang mengembalikan angka, dan kami dapat membandingkan angka. Kami menggambar grafik dan mencatat bahwa solusi yang awalnya ingin kami dapatkan adalah minimum.

Punya masalah pemrograman linier . Inilah yang dilakukan Auto Layout dengan kendala, yang tidak hanya dalam bentuk persamaan, tetapi juga ketidaksetaraan.

Kendala ketimpangan


Dalam kasus pembatasan ketimpangan, transformasi terjadi dengan cara yang sama dengan persamaan: dua variabel tambahan diperkenalkan dan semua ini dikumpulkan dalam sistem. Hanya fungsional yang berbeda, dan sama dengan - .



Grafik di atas menunjukkan mengapa demikian - nilai + dengan a - = 0 (dari X 0 hingga + ∞ ) akan menjadi solusi optimal untuk masalah tersebut.

Mari kita coba menggabungkan dua batasan persamaan dan ketidaksetaraan ini menjadi satu - karena pembatasan tidak hidup dalam isolasi, mereka diterapkan bersama ke seluruh sistem.



Untuk setiap kendala, sepasang variabel tambahan diperkenalkan, dan fungsional dikompilasi. Karena kami ingin semua batasan ini dipenuhi secara bersamaan, fungsional akan sama dengan jumlah semua fungsional dari setiap batasan .

Kami mengumpulkan fungsi f dan melihat solusinya adalah X 1 . Seperti yang kami harapkan, membuat batasan. Jadi kita sampai pada algoritma ketiga.

Algoritma No. 3


Untuk melakukan sesuatu, Anda perlu:

  • membuat sistem kendala linier;
  • mengubahnya menjadi masalah pemrograman linier;
  • memecahkan masalah dengan cara apa pun yang diketahui, misalnya, metode simpleks yang digunakan dalam Tata Letak Otomatis;
  • terapkan solusi untuk UIView.

Algoritma ini tampaknya cukup, tetapi pertimbangkan kasus berikut: kami mengubah set awal kendala sehingga kendala kedua sekarang X ≥ X 2 .



Solusi apa yang kita harapkan untuk dilihat?

  • X 1 ? Memang, dalam batasan pertama ditulis demikian: X = X 1 , dan solusi ini bertentangan dengan pembatasan kedua.
  • X 2 ? Akan ada konflik dengan pembatasan pertama.

Untuk keluar dari situasi tersebut, kami akan melakukan transformasi yang sudah kami ketahui caranya.

Grafik fungsi baru terlihat berbeda: titik apa pun dari interval dari X 1 hingga X 2 akan menjadi solusi yang benar dari sistem. Ini disebut ketidakpastian .

Ketidakpastian


Tata Letak Otomatis memiliki mekanisme untuk memecahkan masalah seperti itu - prioritas . Saya mengingatkan Anda bahwa kuning akan menunjukkan prioritas tinggi, dan biru - rendah.



Konversi pembatasan. Harap dicatat bahwa sistem yang dihasilkan hanya hitam. Kami tahu cara bekerja dengannya, dan tidak ada informasi tentang batasan di dalamnya. Ada dalam fungsi, yang akan ada dua. Tata Letak Otomatis pertama-tama akan meminimalkan yang pertama dan kemudian yang kedua.

Dalam masalah pemrograman linier, kami mencari bukan untuk solusi itu sendiri, tetapi untuk berbagai solusi yang layak. Tentu saja, kami ingin area ini hanya satu titik, dan Tata Letak Otomatis bertindak dengan cara yang sama. Pertama, meminimalkan fungsional prioritas tertinggi pada ( - ∞, + ∞) dan, pada output, menerima domain solusi yang layak. Tata Letak Otomatis memecahkan masalah pemrograman linier kedua yang sudah ada pada kisaran nilai yang diizinkan yang diperoleh. Mekanisme seperti ini disebut hierarki kendala , dan dalam masalah ini memberikan poin X2 .

Algoritma No. 4


  • Buat hierarki kendala linier;
  • mengubahnya menjadi tugas pemrograman linier;
  • memecahkan secara berurutan masalah pemrograman linier - dari prioritas tertinggi ke prioritas terendah.
  • terapkan solusi untuk UlView.

Mari kita lihat kembali tugas sebelumnya. Kami bukan ahli matematika, tetapi insinyur, dan insinyur mana pun harus bingung di sini.

Ada masalah serius di sini - tak terbatas , dan saya tidak tahu apa itu.

Algoritma Cassowary di bawah kap Auto Layout bukanlah mekanisme yang ada yang dengan mudah jatuh pada tugas Tata Letak Otomatis, tetapi dianggap sebagai alat tata letak, dan itu menyediakan mekanisme khusus untuk melarikan diri dari infinity di awal. Untuk ini, beberapa jenis pembatasan ditemukan:

  • Parameter adalah kendala yang telah kami kerjakan. Mereka disebut preferensi dalam aslinya , kadang-kadang dalam dokumentasi Apple - batasan opsional .
  • Persyaratan atau persyaratan - pembatasan dengan prioritas yang diperlukan .

Mari kita lihat bagaimana persyaratan dengan prioritas tersebut ditransformasikan dari sudut pandang matematika.



Kami lagi memiliki garis lurus dengan dua poin, dan batasan pertama adalah X = X 1 . Pada slide, warnanya merah, yaitu pembatasan ini dengan prioritas yang diperlukan - kami akan menyebutnya persyaratan.

Tata Letak Otomatis mengubahnya menjadi sistem persamaan linear yang berisi satu persamaan X = X 1 . Tidak lebih - tidak ada tugas pemrograman linier, tidak ada optimasi.

Situasinya mirip dengan ketidaksetaraan, tetapi sedikit lebih rumit - variabel tambahan akan muncul yang dapat mengambil nilai lebih dari 0. Untuk nilai apa pun yang lebih besar dari 0, batasan ini akan dipenuhi. Harap perhatikan bahwa di sini tidak ada tugas dan optimisasi pemrograman linier.

Mari kita coba menggabungkan semua ini bersama-sama, mengumpulkan dua persyaratan dan mengubahnya menjadi satu sistem. Seorang pembaca yang penuh perhatian, mencatat bahwa kami datang ke masalah yang sama dengan yang kami mulai - persyaratan harus konsisten .



Kendala dari jenis yang dibutuhkan atau persyaratan adalah alat yang sangat kuat, tetapi bukan yang utama, tetapi alat bantu. Ini khusus diperkenalkan di Tata Letak Otomatis untuk memecahkan masalah interval tak terbatas, harus digunakan dengan hati-hati.

Mari kita coba menggabungkan semua jenis batasan yang kita temui dalam satu sistem. Misalkan kita ingin menyelesaikan masalah bukan pada seluruh baris, tetapi hanya antara X 0 dan X 3 . Mengubah semua ini menjadi sistem persamaan linear dan ketidaksetaraan, kami mendapatkan yang berikut ini.



Relatif terhadap sistem sebelumnya, dua variabel tambahan ditambahkan - c dan d , tetapi mereka tidak akan masuk ke fungsional, karena pembatasan dari jenis yang diperlukan tidak mempengaruhi fungsional dalam bentuk aslinya.

Tampaknya tugasnya tidak banyak berubah - kami meminimalkan yang sama seperti sebelumnya, tetapi rentang awal nilai yang dapat diterima berubah, sekarang dari X 0 ke X 3 .

Dari sudut pandang matematis, persyaratan - batasan dari tipe yang diperlukan - adalah kemampuan untuk memperkenalkan persamaan tambahan ke dalam sistem tanpa memodifikasi fungsinya.

Anda harus sangat berhati-hati dengan ini, karena penyalahgunaan berlebihan dari batasan yang diperlukan akan menyebabkan masalah tanpa solusi , dan Tata Letak Otomatis tidak akan mengatasinya.

Kami tiba di algoritma kelima terakhir.

Algoritma No. 5


  • Tetapkan batasan yang diperlukan - persyaratan tata letak;
  • membuat hierarki kendala linier;
  • mengubah semua kendala menjadi masalah pemrograman linier;
  • memecahkan masalah pemrograman linier;
  • terapkan solusi untuk UlView.

Kami memeriksa Cassowary - sebuah algoritma yang ada di dalam Auto Layout, tetapi ketika itu diterapkan, berbagai fitur muncul.

Fitur IOS


Tidak ada perhitungan di layoutSubviews () .

Kapan mereka diproduksi? Jawab: selalu, kapan saja Auto Layout dihitung. Penghitungan terjadi tepat ketika kita menambahkan kendala ke tampilan kita, atau mengaktifkannya menggunakan metode API modern untuk bekerja dengan kendala.



Pandangan kami berbentuk persegi panjang, tetapi masalahnya adalah bahwa informasi ini tidak terkandung di dalam Kasuari, perlu juga ditambahkan di sana. Kami memiliki mekanisme untuk memperkenalkan batasan tambahan. Jika kami memperkenalkan untuk setiap tampilan serangkaian kendala dengan lebar dan tinggi positif, maka kami akan selalu mendapatkan persegi panjang di output. Itulah sebabnya kami tidak dapat mengimbangi tampilan Tata Letak Otomatis dengan dimensi negatif.

Fitur kedua adalah intrinsicContentSize - ukuran intrinsik yang dapat diatur untuk setiap tampilan.



Ini adalah antarmuka sederhana untuk membuat 4 kendala ketimpangan tambahan yang akan ditempatkan dalam sistem. Mekanisme ini sangat nyaman, memungkinkan Anda untuk mengurangi jumlah pembatasan eksplisit, yang menyederhanakan penggunaan Tata Letak Otomatis. Poin terakhir dan tertipis yang sering dilupakan adalah TranslateAutoresizingMaskIntoConstraints.



Ini adalah penopang yang diperkenalkan kembali pada zaman iOS 5, sehingga kode lama tidak rusak setelah munculnya Tata Letak Otomatis.

Bayangkan sebuah situasi: kami memaksakan pandangan pada kendala. Di dalam tampilan, kami menggunakan tampilan, yang tidak tahu apa-apa tentang kendala, semua mengeset pada bingkai, tapi di dalamnya mengetikan tampilan, yang telah lama diterjemahkan ke dalam kendala.

Saya mengingatkan Anda bahwa tidak ada bingkai yang masuk ke dalam tugas Tata Letak Otomatis Kasuari, hanya batasan.

Ukuran dan posisi tampilan yang runtuh pada bingkai tidak sepenuhnya ditentukan melalui kendala. Saat menghitung ukuran dan posisi semua tampilan lain, ukuran yang salah akan diperhitungkan, meskipun setelah Tata Letak Otomatis kami akan menerapkan bingkai yang benar di sana.

Untuk menghindari situasi ini, jika nilai variabel TranslateAutoresizingMaskIntoConstraints benar, maka batasan tambahan diterapkan untuk setiap tampilan yang diletakkan pada bingkai. Rangkaian pembatasan ini dapat bervariasi dari satu jalankan ke menjalankan. Hanya satu hal yang diketahui tentang set ini - bingkainya akan menjadi yang ditransmisikan.

Kompatibilitas antara kode lama yang ditulis tanpa kendala dan kode baru yang ditulis dengan kendala sering kali dapat menderita karena penyalahgunaan properti ini. Pembatasan ini tentu memiliki prioritas persyaratan, jadi jika kita tiba-tiba memaksakan batasan pada pandangan seperti itu, yang memiliki prioritas sangat tinggi, misalnya persyaratan, kita dapat secara tidak sengaja membuat sistem yang tidak konsisten yang tidak akan memiliki solusi.

Penting untuk diketahui:

  • Jika kami membuat tampilan dari Interface Builder , maka nilai default untuk properti ini akan salah .
  • Jika kita membuat tampilan langsung dari kode, maka itu akan benar .

Idenya sangat sederhana - kode lama di mana tampilan dibuat tidak tahu apa-apa tentang Tata Letak Otomatis, dan itu perlu untuk membuatnya sehingga jika tampilan digunakan di suatu tempat di tempat baru, maka itu akan berfungsi.

Kiat praktis


Akan ada tiga dewan di semua dan mulai dengan yang paling penting.

Optimasi


Penting untuk melokalisasi masalah.

Apakah Anda pernah menghadapi masalah mengoptimalkan layar, yang ditata di Tata Letak Otomatis? Kemungkinan besar tidak, lebih sering Anda menghadapi masalah mengoptimalkan tata letak sel di dalam tabel atau Tampilan Koleksi .

Tata Letak Otomatis dioptimalkan cukup untuk membuat layar dan antarmuka apa pun, tetapi membuat 50 atau 100 sekaligus adalah masalah baginya. Untuk melokalkan dan mengoptimalkannya, mari kita lihat eksperimen. Angka-angka tersebut diambil dari sebuah artikel di mana Kasuari pertama kali dijelaskan.


Tugasnya adalah ini: kami membuat rantai tampilan satu per satu, dan kami menghubungkan setiap rantai berikutnya dengan rantai sebelumnya. Dengan demikian, urutan 1000 elemen dibangun. Setelah mengukur berbagai operasi, waktu ditunjukkan dalam milidetik. Nilai-nilainya cukup besar, karena Tata Letak Otomatis ditemukan di persimpangan tahun 80-an dan 90-an.

Dengan mengumpulkan rantai seperti itu, Anda dapat bertindak sebagai berikut:

  • Tambahkan kendala satu per satu dan putuskan setiap waktu. Ini akan memakan waktu 38 detik.
  • Anda dapat menambahkan semua batasan sekaligus , dan hanya kemudian menyelesaikan sistem. Solusi ini lebih efisien. Menurut data lama, efisiensi meningkat sebesar 70%, tetapi dalam implementasi saat ini pada perangkat modern hanya akan ada 20%. Tetapi penambahan pembatasan satu kali secara kualitatif akan selalu lebih efektif.
  • Ketika seluruh rantai terpasang, Anda dapat menambahkan satu batasan lagi . Seperti dapat dilihat dari tabel, operasi ini cukup murah.
  • Hal yang paling menarik: jika kita tidak menambahkan batasan baru, tetapi mengubah beberapa konstanta di salah satu yang ada , ini adalah urutan besarnya lebih efektif daripada menghapus atau membuat batasan baru.

Dua poin pertama dapat digambarkan sebagai perhitungan utama antarmuka, dua yang terakhir - sebagai yang berikutnya.

Perhitungan antarmuka primer


Di sini Anda dapat menggunakan metode penambahan batasan massal untuk optimisasi:

  • NSLayoutConstraints.activate (_ :) - saat membuat tampilan, kumpulkan semua kendala secara berurutan ke dalam array, cache dan baru kemudian menambahkannya sekaligus.
  • Atau buat sel di Interface Builder. Dia akan melakukan segalanya untuk kita, dan melakukan optimasi tambahan, yang sering kali nyaman.

Perhitungan antarmuka selanjutnya


Menambah atau memodifikasi kendala adalah operasi yang kompleks, jadi lebih baik untuk tidak mengubah set kendala, tetapi hanya mengubah konstanta pada kendala yang ada. :

  • UIView — . view , Auto Layout. , , view, .
  • — IntrinsicContentSize. , , .
  • . , , .

, WWDC 2018S220 High Performance Auto Layout . — Apple , .



, constraints.



required , — , , . .

, .

:

  • , . ( loader) — .
  • , . , .




, , .

constraint required, . , , , . , .

, . , — . - , , . Auto Layout , .

, . . , , , . layout, , , . .



, Auto Layout, , .

, required, , . :

  • , .
  • , , .

, , , .

, . , , , . , . .

— . , , , — , — required -.

:

Solving Linear Arithmetic Constraints for User Interface Applications
The Cassowary Linear Constraint Solving Algorithm
Constraints as s Design Pattern
Auto Layout Guide by Apple
WWDC 2018 Session 220 High Performance Auto Layout
UILabel API Auto Layout —
. Medium

Omong-omong, kami telah menerima laporan Anton di program AppsConf 2019 . Biarkan saya mengingatkan Anda bahwa kami telah memindahkan AppsConf dari musim gugur ke musim semi, dan konferensi paling berguna berikutnya untuk pengembang seluler akan diadakan pada 22 dan 23 April. Sudah waktunya untuk memikirkan topik untuk presentasi dan menyerahkan laporan , atau berdiskusi dengan pemimpin tentang pentingnya pergi ke konferensi dan memesan tiket.

Source: https://habr.com/ru/post/id437584/


All Articles