Mode offline di iOS dan fitur implementasinya di Realm



Diposting oleh Ekaterina Semashko, Pengembang iOS Strong Junior, DataArt

Sedikit tentang proyek: aplikasi mobile untuk platform iOS, ditulis dalam Swift. Tujuan dari aplikasi ini adalah kemampuan untuk berbagi kartu diskon antara karyawan perusahaan dan teman-teman mereka.

Salah satu tujuan proyek ini adalah untuk mempelajari dan mempraktikkan teknologi dan perpustakaan populer. Realm dipilih untuk menyimpan data lokal, Alamofire digunakan untuk bekerja dengan server, Google Sign-In digunakan untuk otentikasi, PINRemoteImage digunakan untuk mengunggah gambar.

Fungsi utama aplikasi:

  • menambahkan peta, mengedit, dan menghapusnya;
  • melihat kartu orang lain;
  • mencari kartu dengan nama toko / nama pengguna;
  • Tambahkan kartu ke favorit Anda untuk akses cepat.

Kemampuan untuk menggunakan aplikasi tanpa menghubungkan ke jaringan diasumsikan sejak awal, tetapi hanya dalam mode baca. Yaitu kami dapat melihat informasi tentang kartu, tetapi tidak dapat memodifikasinya tanpa internet. Untuk ini, aplikasi selalu memiliki salinan semua kartu dan merek database dari server, ditambah daftar favorit untuk pengguna saat ini. Pencarian juga dilaksanakan secara lokal.

Kemudian kami memutuskan untuk memperluas offline dengan menambahkan mode perekaman. Informasi tentang perubahan yang dilakukan oleh pengguna disimpan dan disinkronkan ketika koneksi Internet muncul. Implementasi mode offline baca-tulis akan dibahas.



Apa yang diperlukan untuk mode offline penuh dalam aplikasi seluler? Kami perlu menghapus ketergantungan pengguna pada kualitas koneksi Internet, khususnya:

  1. Hapus ketergantungan respons kepada pengguna pada tindakannya di UI dari server. Pertama-tama, permintaan akan berinteraksi dengan penyimpanan lokal, kemudian akan dikirim ke server.
  2. Tandai dan simpan perubahan lokal.
  3. Terapkan mekanisme sinkronisasi - ketika koneksi Internet muncul, Anda perlu mengirim perubahan ke server.
  4. Tampilkan kepada pengguna perubahan mana yang disinkronkan, mana yang tidak.

Pendekatan offline-pertama


Pertama-tama, saya harus mengubah mekanisme yang ada untuk berinteraksi dengan server dan database. Tujuannya adalah untuk mencegah pengguna bergantung pada ada atau tidak adanya Internet. Pertama-tama, itu harus berinteraksi dengan gudang data lokal, dan permintaan server harus di latar belakang.

Di versi sebelumnya, ada koneksi yang kuat antara lapisan penyimpanan data dan lapisan jaringan. Mekanisme untuk bekerja dengan data adalah sebagai berikut: pertama permintaan dibuat ke server melalui kelas NetworkManager, kami menunggu hasilnya, setelah itu data disimpan ke database melalui kelas Repository. Kemudian hasilnya diberikan kepada UI, seperti yang ditunjukkan pada diagram.


Untuk menerapkan pendekatan offline-pertama, saya memisahkan lapisan penyimpanan data dan lapisan jaringan, memperkenalkan kelas Flow baru yang mengontrol urutan di mana NetworkManager dan Repositori dipanggil. Sekarang data pertama kali disimpan ke database melalui kelas Repository, kemudian hasilnya dikirim ke UI, dan pengguna terus bekerja dengan aplikasi tersebut. Di latar belakang, permintaan dibuat ke server, setelah respons, informasi dalam database dan UI diperbarui.


Bekerja dengan pengidentifikasi objek


Dengan arsitektur baru, beberapa tugas baru muncul, salah satunya bekerja dengan objek id. Sebelumnya, kami menerimanya dari server saat membuat objek. Tetapi sekarang objek dibuat secara lokal, oleh karena itu, perlu untuk menghasilkan id dan setelah sinkronisasi memperbaruinya dengan yang saat ini. Di sini saya menemukan batasan pertama Realm: setelah membuat objek, Anda tidak dapat mengubah kunci utamanya.

Opsi pertama adalah meninggalkan kunci utama di objek, menjadikan id bidang biasa. Tetapi pada saat yang sama, keuntungan menggunakan kunci utama hilang: Pengindeksan realm, yang mempercepat pengambilan objek, kemampuan untuk memperbarui objek dengan flag create (membuat objek jika tidak ada), dan kepatuhan dengan keunikan objek.

Saya ingin menyimpan kunci utama, tetapi itu tidak bisa menjadi id objek dari server. Akibatnya, solusi yang berfungsi adalah memiliki dua pengidentifikasi, salah satunya server, bidang opsional, dan lokal kedua, yang akan menjadi kunci utama.

Akibatnya, id lokal dihasilkan pada klien saat membuat objek secara lokal, dan dalam kasus ketika objek berasal dari server, itu sama dengan id server. Karena dalam sumber tunggal aplikasi kebenaran ada database, ketika menerima data dari server, objek diperbarui dengan pengenal lokal saat ini, dan hanya bekerja dengannya. Saat mengirim data ke server, pengidentifikasi server dikirimkan.

Penyimpanan perubahan yang tidak disinkronkan


Perubahan ke objek yang belum dikirim ke server harus disimpan secara lokal. Ini dapat diimplementasikan dengan cara-cara berikut:

  1. Menambahkan bidang ke objek yang ada
  2. menyimpan objek yang tidak disinkronkan dalam tabel terpisah;
  3. menyimpan perubahan bidang individual dalam beberapa format.


Saya tidak menggunakan objek Realm langsung di kelas saya, tetapi saya melakukan pemetaan sendiri untuk menghindari masalah dengan multithreading. Antarmuka pembaruan otomatis dilakukan menggunakan sampel hasil pembaruan otomatis, tempat saya berlangganan permintaan pembaruan. Hanya pendekatan pertama yang bekerja dengan arsitektur saya saat ini, sehingga pilihan jatuh pada penambahan bidang ke objek yang ada.

Objek peta telah mengalami banyak perubahan:

  • disinkronkan - apakah ada data di server;
  • dihapus - benar, jika kartu hanya dihapus secara lokal, diperlukan sinkronisasi.

Pengidentifikasi yang dibahas di bagian sebelumnya:

  • localId - kunci utama entitas dalam aplikasi, baik sama dengan server id, atau dihasilkan secara lokal;
  • serverId - id dari server.

Secara terpisah layak disebutkan adalah penyimpanan gambar. Pada intinya, bidang lampiran diskURL telah ditambahkan ke bidang serverURL gambar di server, yang menyimpan alamat gambar tidak disinkronkan lokal. Saat menyinkronkan gambar, yang lokal dihapus agar tidak menyumbat memori perangkat.

Sinkronisasi server


Untuk menyinkronkan dengan server, bekerja dengan Reachability ditambahkan, sehingga ketika Internet muncul, mekanisme sinkronisasi dimulai.

Pertama, ia memeriksa untuk melihat apakah ada perubahan pada database yang perlu dikirimkan. Kemudian, permintaan dikirim ke server untuk data cast aktual, sebagai akibatnya, perubahan yang tidak perlu dikirim ke klien disaring (misalnya, mengubah objek yang telah dihapus di server). Perubahan yang tersisa mengantri permintaan ke server.

Untuk mengirim perubahan, dimungkinkan untuk mengimplementasikan pembaruan massal, mengirimkan perubahan dalam array, atau membuat permintaan besar untuk menyinkronkan semua data. Tetapi pada saat itu, pengembang backend sudah sibuk di proyek lain, dan hanya membantu kami di waktu luang kami, jadi kami membuat permintaan untuk setiap jenis perubahan.

Saya menerapkan antrian melalui OperationQueue dan membungkus setiap permintaan dalam Operasi asinkron. Beberapa operasi saling bergantung, misalnya, kita tidak dapat memuat gambar peta sebelum membuat peta, jadi saya menambahkan ketergantungan operasi gambar ke operasi peta. Juga, operasi mengunggah gambar ke server diberi prioritas lebih rendah daripada orang lain, dan saya menambahkannya ke antrian juga bertahan karena beratnya mereka.

Saat merencanakan mode offline, pertanyaan besarnya adalah menyelesaikan konflik dengan server selama sinkronisasi. Tetapi ketika kami sampai pada titik ini selama implementasi, kami menyadari bahwa kasus ketika pengguna mengubah data yang sama pada perangkat yang berbeda sangat jarang. Jadi itu cukup bagi kita untuk menerapkan mekanisme menang penulis terakhir. Selama sinkronisasi, prioritas selalu diberikan pada perubahan yang tidak terkirim pada klien, mereka tidak digosok.

Penanganan kesalahan masih dalam tahap awal, jika sinkronisasi gagal, objek akan ditambahkan ke antrian perubahan saat Internet muncul. Dan kemudian jika itu masih hang dari sinkronisasi setelah penggabungan, pengguna akan memutuskan apakah akan meninggalkannya atau menghapusnya.

Solusi tambahan saat bekerja dengan Realm


Ketika bekerja dengan Realm dihadapkan dengan beberapa masalah lagi. Mungkin pengalaman ini juga bermanfaat bagi seseorang.

Saat mengurutkan berdasarkan string, urutan berjalan sesuai dengan urutan karakter di UTF-8, tidak ada dukungan pencarian yang case-sensitive. Kita dihadapkan dengan situasi di mana nama dalam huruf kecil datang setelah nama dalam huruf besar, misalnya: Magnet, Pyaterochka, Ribbon. Jika daftar ini sangat besar, semua nama dalam huruf kecil akan berada di bagian bawah, yang sangat tidak menyenangkan.

Untuk mempertahankan urutan pengurutan, terlepas dari kasusnya, kami harus memperkenalkan bidang lowercasedName baru, memperbaruinya saat memperbarui nama dan mengurutkannya.

Selain itu, bidang baru telah ditambahkan untuk mengurutkan dengan adanya kartu di favorit, karena pada dasarnya ini membutuhkan subquery untuk hubungan objek.

Saat mencari di Realm, ada metode CONTAIN [c]% @ untuk pencarian yang tidak case-sensitive. Tapi, sayangnya, itu hanya bekerja dengan alfabet Latin. Untuk merek Rusia, kami juga harus membuat bidang terpisah dan mencarinya. Tetapi kemudian ternyata berada di tangan kita untuk mengecualikan karakter khusus saat mencari.



Seperti yang Anda lihat, untuk aplikasi seluler sangat mungkin untuk menerapkan mode offline dengan menyimpan perubahan dan menyinkronkan dengan sedikit darah, dan kadang-kadang bahkan dengan perubahan minimal di backend.

Meskipun ada beberapa kesulitan, Anda dapat menggunakan Realm untuk mengimplementasikannya, sambil menerima semua keuntungan dalam bentuk pembaruan langsung, arsitektur zero-copy dan API yang nyaman.

Jadi tidak ada alasan untuk menolak akses pengguna Anda ke data setiap saat, terlepas dari kualitas koneksi.

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


All Articles