Setiap tahun, platform iOS mengalami banyak perubahan, di samping itu, perpustakaan pihak ketiga secara teratur bekerja dengan jaringan, menyimpan data, membuat UI melalui JavaScript, dan banyak lagi. Berbeda dengan semua tren ini,
Pavel Gurov berbicara tentang solusi arsitektur, yang akan relevan terlepas dari teknologi apa yang Anda gunakan sekarang atau akan digunakan dalam beberapa tahun.
ApplicationCoordinator dapat digunakan untuk membangun navigasi antar layar, dan pada saat yang sama menyelesaikan sejumlah masalah. Di bawah demo kucing dan instruksi untuk implementasi tercepat dari pendekatan ini.
Tentang pembicara: Pavel Gurov sedang mengembangkan aplikasi iOS di Avito.
Navigasi

Menavigasi antar layar adalah tugas yang 100% Anda hadapi, apa pun yang Anda lakukan - jejaring sosial, panggilan taksi, atau bank online. Inilah aplikasi yang dimulai dengan bahkan pada tahap prototipe, ketika Anda bahkan tidak tahu sepenuhnya seperti apa layarnya, seperti apa bentuk animasi mereka, apakah data akan di-cache. Layar mungkin berupa gambar kosong atau statis, tetapi
tugas navigasi muncul di aplikasi segera setelah ada lebih dari satu layar ini . Yaitu, segera.

Metode paling umum untuk membangun arsitektur aplikasi iOS: MVc, MVVm, dan MVp, menjelaskan cara membuat modul layar tunggal. Ia juga mengatakan bahwa modul-modul tersebut dapat saling mengenal, berkomunikasi satu sama lain, dll. Tetapi sangat sedikit perhatian diberikan pada masalah bagaimana transisi antara modul-modul ini dibuat, siapa yang memutuskan transisi ini, dan bagaimana data ditransmisikan.
UlStoryboard + segues
iOS out of the box menyediakan beberapa cara untuk menunjukkan skenario layar berikut:
- UlStoryboard + segues yang terkenal , ketika kami menetapkan semua transisi antar layar dalam satu file-meta, dan kemudian memanggilnya. Semuanya sangat nyaman dan hebat.
- Kontainer - seperti UINavigationController. UITabBarController, UIPageController atau, mungkin, wadah yang ditulis sendiri yang dapat digunakan baik secara programatis dan bersama dengan StoryBoards.
- Metode sekarang (_: animasi: selesai :). Ini hanya metode kelas UIController.
Tidak ada masalah dengan alat ini sendiri. Masalahnya persis bagaimana mereka biasa digunakan. UINavigationController, performSegue, prepForSegue, metode presentViewController adalah semua metode properti dari kelas UIViewController. Apple menyarankan untuk menggunakan alat-alat ini di dalam UIViewController itu sendiri.

Buktinya adalah sebagai berikut.

Ini adalah komentar yang muncul di proyek Anda jika Anda membuat subkelas baru dari UIViewController menggunakan templat standar. Ini ditulis secara langsung - jika Anda menggunakan segues dan Anda perlu mentransfer data ke layar berikutnya sesuai dengan skenario, Anda harus: mendapatkan ViewController ini dari segue; tahu apa jenisnya; masukkan ke tipe ini dan berikan data Anda di sana.
Pendekatan ini untuk masalah dalam membangun navigasi.
1. Konektivitas layar yang kakuIni berarti bahwa layar 1 tahu tentang keberadaan layar 2. Tidak hanya dia tahu tentang keberadaannya, itu juga berpotensi membuatnya, atau mengambilnya dari segue, mengetahui jenisnya, dan mentransfer beberapa data ke sana.
Jika kita perlu, dalam beberapa keadaan, untuk menampilkan layar 3 daripada layar 2, maka kita harus tahu tentang layar baru 3 dengan cara yang sama untuk dijahit ke pengontrol layar 1. Semuanya menjadi lebih sulit jika pengontrol 2 dan 3 dapat dipanggil dari beberapa tempat lagi, tidak hanya dari layar 1. Ternyata pengetahuan layar 2 dan 3 harus dijahit di setiap tempat ini.
Untuk melakukan ini adalah setengah dari masalah, masalah utama akan dimulai ketika perlu untuk melakukan perubahan pada transisi ini, atau untuk mendukung semua ini.
2. Susun ulang pengontrol skripIni juga tidak begitu sederhana karena koneksi. Untuk menukar dua ViewControllers, tidak akan cukup untuk masuk ke UlStoryboard dan menukar 2 gambar. Anda harus membuka kode untuk masing-masing layar ini, mentransfernya ke pengaturan yang berikutnya, dan mengubah tempatnya, yang sangat tidak nyaman.
3. Transfer data sesuai dengan skenarioMisalnya, ketika memilih sesuatu di layar 3, kita perlu memperbarui Tampilan di layar 1. Karena pada awalnya kita tidak memiliki apa-apa selain ViewController, kita harus entah bagaimana menghubungkan dua ViewControllers - tidak masalah bagaimana - melalui delegasi atau entah bagaimana belum. Akan lebih sulit lagi jika, menurut aksi pada layar 3, perlu memperbarui bukan satu layar, tetapi beberapa sekaligus, misalnya, yang pertama dan kedua.

Dalam hal ini, delegasi tidak dapat diabaikan, karena delegasi adalah hubungan satu-ke-satu. Seseorang akan berkata, mari kita gunakan pemberitahuan, seseorang - melalui status bersama. Semua ini membuat sulit untuk men-debug dan melacak aliran data dalam aplikasi kita.
Seperti yang mereka katakan, lebih baik melihat sekali daripada mendengar 100 kali. Mari kita lihat contoh spesifik dari aplikasi Avito Services Pro ini. Aplikasi ini untuk para profesional di sektor layanan, di mana nyaman untuk melacak pesanan Anda, berkomunikasi dengan pelanggan, mencari pesanan baru.
Skenario - memilih kota dalam mengedit profil pengguna.

Ini adalah layar pengeditan profil, seperti yang ada di banyak aplikasi. Kami tertarik untuk memilih kota.
Apa yang sedang terjadi di sini?
- Pengguna mengklik sel dengan kota, dan layar pertama memutuskan bahwa sudah waktunya untuk menambahkan layar berikut ke tumpukan navigasi. Ini adalah layar dengan daftar kota-kota federal (Moskow dan St. Petersburg) dan daftar wilayah.
- Jika pengguna memilih kota federal pada layar kedua, maka layar kedua memahami bahwa skrip telah selesai, meneruskan kota yang dipilih ke yang pertama dan tumpukan Navigasi kembali ke layar pertama. Script dianggap lengkap.
- Jika pengguna memilih area di layar kedua, maka layar kedua memutuskan bahwa layar ketiga perlu disiapkan, di mana kita melihat daftar kota di daerah ini. Jika pengguna memilih kota, maka kota ini dikirim ke layar pertama, menggulung tumpukan Navigasi dan skrip dianggap lengkap.
Dalam diagram ini, masalah konektivitas yang saya sebutkan sebelumnya ditampilkan sebagai panah di antara ViewController. Kami akan menyingkirkan masalah ini sekarang.
Bagaimana kita melakukan ini?- Kami melarang diri di dalam UIViewController untuk mengakses kontainer , yaitu ke self.navigationController, self.tabBarController, atau beberapa kontainer kustom lain yang Anda buat sebagai ekstensi properti. Sekarang kita tidak bisa mengambil wadah kita dari kode layar dan memintanya untuk melakukan sesuatu.

- Kami melarang diri kami di dalam UIViewController untuk memanggil metode performSegue dan menulis kode dalam metode prepForSegue, yang akan mengambil layar yang mengikuti skrip dan mengkonfigurasinya. Artinya, kami tidak lagi bekerja dengan segue (dengan transisi antar layar) di dalam UIViewController.

- Kami juga melarang penyebutan pengontrol lain di dalam pengontrol khusus kami : tidak ada inisialisasi, transfer data, dan hanya itu.

Koordinator
Karena kita menghapus semua tanggung jawab ini dari UIViewController, kita memerlukan entitas baru yang akan menjalankannya. Buat kelas objek baru, dan menyebutnya koordinator.

Koordinator hanyalah objek biasa yang kita lewati pada awal NavigationController dan memanggil metode Mulai. Sekarang jangan berpikir tentang bagaimana ini diterapkan, lihat saja bagaimana skenario memilih kota akan berubah dalam kasus ini.
Sekarang ini tidak dimulai dengan fakta bahwa kami sedang mempersiapkan transisi ke layar NavigationController tertentu, tetapi kami memanggil metode Start di koordinator, meneruskannya sebelum itu di penginisialisasi NavigationController. Koordinator memahami bahwa sudah waktunya bagi NavigationController untuk meluncurkan layar pertama, yang ia lakukan.
Selanjutnya, ketika pengguna memilih sel dengan kota, acara ini diteruskan ke koordinator. Artinya, layar itu sendiri tidak tahu apa-apa - setelah itu, seperti yang mereka katakan, setidaknya banjir. Dia mengirim pesan ini ke koordinator, dan kemudian koordinator bereaksi dengan (karena dia memiliki NavigationController), yang mengirimkan langkah selanjutnya - ini adalah pilihan daerah.
Selanjutnya, pengguna mengklik "Wilayah" - gambar yang sama persis - layar itu sendiri tidak menyelesaikan apa pun, hanya memberi tahu koordinator bahwa layar berikutnya terbuka.
Ketika pengguna memilih kota tertentu pada layar ketiga, kota ini juga ditransfer ke layar pertama melalui koordinator. Yaitu, sebuah pesan dikirimkan kepada koordinator bahwa suatu kota telah dipilih. Koordinator mengirim pesan ini ke layar pertama dan melempar tumpukan Navigasi ke layar pertama.
Perhatikan bahwa
pengontrol tidak lagi berkomunikasi satu sama lain , memutuskan siapa yang akan menjadi berikutnya, dan tidak mengirimkan data satu sama lain. Apalagi mereka tidak tahu apa-apa tentang lingkungannya.

Jika kita mempertimbangkan aplikasi dalam kerangka arsitektur tiga lapis, maka ViewController idealnya pas sepenuhnya ke dalam lapisan Presentasi dan membawa sesedikit mungkin logika aplikasi.
Dalam hal ini, kami menggunakan koordinator untuk mengeluarkan logika transisi ke lapisan di atas dan menghapus pengetahuan ini dari ViewController.
Demo
Proyek presentasi dan demo tersedia di Github, di bawah ini adalah demonstrasi selama pembicaraan.
Ini adalah skenario yang sama: mengedit profil dan memilih kota di dalamnya.
Layar pertama adalah layar edit pengguna. Ini menunjukkan informasi tentang pengguna saat ini: nama dan kota yang dipilih. Ada tombol "Pilih kota." Ketika kita mengkliknya, kita menuju ke layar dengan daftar kota. Jika kita memilih kota di sana, maka layar pertama mendapatkan kota ini.
Mari kita lihat sekarang bagaimana ini bekerja dalam kode. Mari kita mulai dengan modelnya.
struct City { let name: String } struct User { let name: String var city: City? }
Modelnya sederhana:
- Struktur kota yang memiliki nama bidang, string;
- Seorang pengguna yang juga memiliki nama dan kota properti.
Selanjutnya adalah
StoryBoard . Dimulai dengan NavigationController. Pada prinsipnya, ini adalah layar yang sama dengan yang ada di simulator: layar pengeditan pengguna dengan label dan tombol dan layar dengan daftar kota, yang menunjukkan tablet dengan kota.
Layar Edit Pengguna
import UIKit final class UserEditViewController: UIViewController, UpdateableWithUser { // MARK: - Input - var user: User? { didSet { updateView() } } // MARK: - Output - var onSelectCity: (() -> Void)? @IBOutlet private weak var userLabel: UILabel? @IBAction private func selectCityTap(_ sender: UIButton) { onSelectCity?() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateView() } private func updateView() { userLabel?.text = "User: \(user?.name ?? ""), \n" + "City: \(user?.city?.name ?? "")" } }
Di sini ada Pengguna properti - ini adalah pengguna yang dikirim ke luar - pengguna yang akan kami edit. Setel pengguna di sini menyebabkan blok didSet dipanggil, yang mengarah ke panggilan ke metode updateView () lokal. Semua yang dilakukan metode ini hanyalah menaruh informasi tentang pengguna pada label, yaitu menunjukkan nama dan nama kota tempat tinggal pengguna ini.
Hal yang sama terjadi pada metode viewWillAppear ().
Tempat paling menarik adalah pawang untuk mengklik tombol pilih kota selectCityTap ().
Di sini, pengontrol itu sendiri tidak menyelesaikan apa pun : ia tidak membuat pengontrol apa pun, ia tidak memanggil segue apa pun. Yang dia lakukan hanyalah panggilan balik - ini adalah properti kedua dari ViewController kami. Callback onSelectCity tidak memiliki parameter. Ketika pengguna mengklik tombol, ini menyebabkan panggilan balik ini dipanggil.
Layar pemilihan kota
import UIKit final class CitiesViewController: UITableViewController { // MARK: - Output - var onCitySelected: ((City) -> Void)? // MARK: - Private variables - private let cities: [City] = [City(name: "Moscow"), City(name: "Ulyanovsk"), City(name: "New York"), City(name: "Tokyo")] // MARK: - Table - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return cities.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = cities[indexPath.row].name return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { onCitySelected?(cities[indexPath.row]) } }
Layar ini adalah UITableViewController. Daftar kota di sini sudah diperbaiki, tetapi mungkin datang dari tempat lain. Selanjutnya (// MARK: - Tabel -) adalah kode tabel yang cukup sepele yang menampilkan daftar kota dalam sel.
Tempat paling menarik di sini adalah handler didSelectRowAt IndexPath, metode terkenal untuk semua orang. Di sini layar itu sendiri lagi tidak menyelesaikan apa pun. Apa yang terjadi setelah kota dipilih? Itu hanya memanggil panggilan balik dengan parameter tunggal "kota".
Ini mengakhiri kode untuk layar sendiri. Seperti yang kita lihat, mereka tidak tahu apa-apa tentang lingkungan mereka.
Koordinator
Mari beralih ke tautan di antara layar-layar ini.
import UIKit protocol UpdateableWithUser: class { var user: User? { get set } } final class UserEditCoordinator { // MARK: - Properties private var user: User { didSet { updateInterfaces() } } private weak var navigationController: UINavigationController? // MARK: - Init init(user: User, navigationController: UINavigationController) { self.user = user self.navigationController = navigationController } func start() { showUserEditScreen() } // MARK: - Private implementation private func showUserEditScreen() { let controller = UIStoryboard.makeUserEditController() controller.user = user controller.onSelectCity = { [weak self] in self?.showCitiesScreen() } navigationController?.pushViewController(controller, animated: false) } private func showCitiesScreen() { let controller = UIStoryboard.makeCitiesController() controller.onCitySelected = { [weak self] city in self?.user.city = city _ = self?.navigationController?.popViewController(animated: true) } navigationController?.pushViewController(controller, animated: true) } private func updateInterfaces() { navigationController?.viewControllers.forEach { ($0 as? UpdateableWithUser)?.user = user } } }
Koordinator memiliki dua properti:
- Pengguna - pengguna yang akan kami edit;
- NavigationController yang akan dilewati saat startup.
Ada init sederhana () yang mengisi properti ini.
Berikutnya adalah metode start (), yang menyebabkan metode
ShowUserEditScreen () dipanggil . Mari kita bahas lebih detail. Metode ini mengeluarkan pengontrol dari UIStoryboard, meneruskannya ke pengguna lokal kami. Kemudian dia melakukan panggilan balik "SelectCity" dan mendorong pengontrol ini ke tumpukan Navigasi.
Setelah pengguna mengklik tombol, panggilan balik onSelectCity dipicu, dan ini akan
memanggil metode
ShowCitiesScreen () pribadi berikut.
Bahkan, ia melakukan hal yang hampir sama - ia mengangkat pengontrol yang sedikit berbeda dari UIStoryboard, menempatkan panggilan balik onCitySelected di atasnya dan mendorongnya ke tumpukan Navigasi - itu saja yang terjadi. Ketika pengguna memilih kota tertentu, panggilan balik ini dipicu, koordinator memperbarui bidang "kota" dari pengguna lokal kami dan melempar tumpukan Navigasi ke layar pertama.
Karena Pengguna adalah struktur, memperbarui bidang "kota" di dalamnya mengarah pada fakta bahwa blok didSet dipanggil, masing-masing, metode pribadi
updateInterfaces () dipanggil. Metode ini melewati seluruh tumpukan Navigasi dan mencoba untuk menyebarkan setiap ViewController sebagai protokol UpdateableWithUser. Ini adalah protokol paling sederhana, yang hanya memiliki satu properti - pengguna. Jika ini berhasil, maka ia melemparnya ke pengguna yang diperbarui. Dengan demikian, ternyata pengguna yang kami pilih di layar kedua secara otomatis melompat ke layar pertama.
Semuanya jelas dengan koordinator, dan satu-satunya yang tersisa untuk ditampilkan di sini adalah titik masuk ke aplikasi kita. Di sinilah semuanya dimulai. Dalam hal ini, ini adalah metode didFinishLaunchingWithOptions di AppDelegate kami.
import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var coordinator: UserEditCoordinator! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { guard let navigationController = window?.rootViewController as? UINavigationController else { return true } let user = User(name: "Pavel Gurov", city: City(name: "Moscow")) coordinator = UserEditCoordinator(user: user, navigationController: navigationController) coordinator.start() return true } }
Di sini navigationController diambil dari UIStoryboard, seorang Pengguna dibuat, yang akan kami edit, dengan nama dan kota tertentu. Selanjutnya kita buat koordinator kita dengan User dan navigationController. Ini memanggil metode start (). Koordinator dipindahkan ke properti lokal - itu pada dasarnya semua. Skema ini cukup sederhana.
Input dan output
Ada beberapa poin yang ingin saya bahas lebih detail. Anda mungkin memperhatikan bahwa properti di userEditViewController ditandai dengan komentar sebagai Input, dan panggilan balik dari pengontrol ini ditandai sebagai Output.
Input adalah semua data yang dapat berubah dari waktu ke waktu, serta beberapa metode ViewController yang dapat dipanggil dari luar. Misalnya, di UserEditViewController ini adalah properti Pengguna - Pengguna itu sendiri atau parameter City-nya dapat berubah.
Keluar adalah peristiwa apa pun yang ingin dikontrol pengontrol ke dunia luar. Di UserEditViewController, ini adalah klik pada tombol onSelectCity, dan pada layar pemilihan kota, ini adalah klik pada sel dengan kota tertentu. Gagasan utama di sini adalah, saya ulangi, bahwa pengontrol tidak tahu apa-apa dan tidak melakukan apa-apa tentang peristiwa ini. Dia mendelegasikan untuk memutuskan apa yang harus dilakukan, kepada orang lain.
Di Objective-C, saya tidak terlalu suka menulis save callbacks karena sintaksisnya yang buruk. Tetapi di Swift, ini jauh lebih sederhana. Menggunakan panggilan balik dalam hal ini merupakan alternatif dari pola delegasi terkenal di iOS. Hanya di sini, alih-alih menetapkan metode dalam protokol dan mengatakan bahwa koordinator sesuai dengan protokol ini, dan kemudian menulis metode ini di suatu tempat secara terpisah, kita dapat segera membuat entitas di satu tempat dengan mudah, melakukan panggilan balik ke sana dan melakukan semuanya.
Benar, dengan pendekatan ini, tidak seperti delegasi, ada hubungan yang erat antara esensi koordinator dan layar, karena koordinator tahu bahwa ada esensi spesifik layar.
Anda dapat menyingkirkan ini dengan cara yang sama seperti dalam delegasi, menggunakan protokol.

Untuk menghindari konektivitas, kita dapat
menutup Input dan Output dari controller kita dengan sebuah protokol .
Di atas adalah protokol CitiesOutput, yang memiliki tepat satu persyaratan - panggilan balik onCitySelected. Di sebelah kiri adalah analog dari skema ini di Swift. Pengontrol kami mematuhi protokol ini, menentukan panggilan balik yang diperlukan. Kami melakukan ini sehingga koordinator tidak tahu tentang keberadaan kelas CitiesViewController. Tetapi pada beberapa titik dia perlu mengkonfigurasi output dari controller ini. Untuk menghidupkan semuanya, kami menambahkan pabrik ke koordinator.

Pabrik memiliki metode cityOutput (). Ternyata koordinator kami tidak membuat pengontrol dan tidak mendapatkannya dari suatu tempat. Sebuah pabrik melemparkannya ke arahnya, yang mengembalikan objek yang ditutup oleh protokol dalam metode, dan dia tidak tahu apa-apa tentang kelas apa objek ini.
Sekarang yang paling penting - mengapa melakukan semua ini?
Mengapa kita perlu membangun level tambahan lain ketika tidak ada masalah?Orang dapat membayangkan situasi ini: seorang manajer akan datang kepada kami dan meminta Anda untuk melakukan pengujian A / B atas fakta bahwa alih-alih daftar kota, kami akan memiliki pilihan kota di peta. Jika dalam aplikasi kami pilihan kota tidak di satu tempat, tetapi di koordinator yang berbeda, dalam skenario yang berbeda, kami harus menjahit bendera di setiap tempat, melemparkannya ke luar, pada bendera ini menaikkan salah satu atau yang lain ViewController. Ini sangat tidak nyaman.
Kami ingin menghapus pengetahuan ini dari koordinator. Karena itu, seseorang dapat melakukan ini di satu tempat. Di pabrik itu sendiri, kita akan membuat parameter di mana pabrik mengembalikan salah satu atau pengontrol lain yang ditutup oleh protokol. Keduanya akan memiliki panggilan balik onCitySelected, dan koordinator pada prinsipnya tidak akan peduli dengan layar mana yang dapat digunakan - peta atau daftar.
Komposisi VS Warisan
Poin berikutnya yang ingin saya bahas adalah komposisi melawan warisan.

- Metode pertama yang dapat dilakukan koordinator kami adalah membuat komposisi ketika NavigationController diteruskan dari luar dan disimpan secara lokal sebagai properti. Ini seperti komposisi - kami menambahkan NavigationController sebagai properti.
- Di sisi lain, ada pendapat bahwa semuanya ada di Kit UI, dan kita tidak perlu menemukan kembali roda. Anda cukup mengambil dan mewarisi UI NavigationController .
Setiap opsi memiliki pro dan kontra, tetapi secara pribadi menurut saya
komposisi dalam kasus ini lebih cocok daripada warisan. Warisan umumnya merupakan skema yang kurang fleksibel. Jika kita perlu, misalnya, untuk mengubah Navigasi ke, katakanlah, UIPageController, maka dalam kasus pertama kita dapat dengan mudah menutupnya dengan protokol umum, seperti "Tampilkan layar berikutnya" dan dengan mudah mengganti wadah yang kita butuhkan.
Dari sudut pandang saya, argumen paling penting adalah bahwa Anda menyembunyikan dari pengguna akhir dalam komposisi semua metode yang tidak perlu. Ternyata dia kurang mungkin tersandung. Anda
hanya menyisakan
API yang diperlukan , misalnya, metode Mulai - dan itu saja. Dia tidak memiliki cara untuk memanggil PushViewController, metode PopViewController, yaitu, entah bagaimana mengganggu kegiatan koordinator. Semua metode dari kelas induk disembunyikan.
Papan cerita
Saya percaya bahwa mereka pantas mendapat perhatian khusus bersama dengan segues. Secara pribadi,
saya mendukung segues , karena memungkinkan Anda dengan cepat membiasakan diri dengan skrip. Ketika seorang pengembang baru tiba, ia tidak perlu memanjat kode, Storyboards membantu dengan ini. Bahkan jika Anda membuat antarmuka dengan kode, Anda dapat meninggalkan ViewController kosong, dan membuat antarmuka dengan kode, tetapi meninggalkan setidaknya transisi dan seluruh poin. Seluruh esensi Storyboards ada dalam transisi sendiri, dan bukan dalam tata letak UI.
Untungnya,
pendekatan koordinator tidak membatasi pilihan alat . Kita dapat dengan aman menggunakan koordinator bersama dengan segues. Tetapi kita harus ingat bahwa sekarang kita tidak dapat bekerja dengan segues di dalam UIViewController.

Oleh karena itu, kita harus mengganti metode onPrepareForSegue di kelas kita. Alih-alih melakukan sesuatu di dalam controller, kami akan mendelegasikan tugas-tugas ini lagi ke koordinator, melalui callback. Metode onPrepareForSegue disebut, Anda tidak melakukan apa pun sendiri - Anda tidak tahu jenis segmen apa itu, apa tujuan pengontrolnya - itu tidak masalah bagi Anda. Anda cukup melemparkan semuanya ke dalam callback, dan koordinator akan mengetahuinya. Dia memiliki pengetahuan ini, Anda tidak membutuhkan pengetahuan ini.
Untuk membuat semuanya lebih sederhana, Anda bisa melakukan ini di kelas Base tertentu agar tidak menimpanya di setiap kontroler yang diambil secara terpisah. Dalam hal ini, koordinator akan lebih nyaman bekerja dengan segmen Anda.
Hal lain yang menurut saya nyaman dengan Storyboard adalah mematuhi aturan bahwa
satu Storyboard sama dengan satu koordinator . Kemudian Anda bisa sangat menyederhanakan semuanya, membuat satu kelas secara umum - StoryboardCoordinator, dan menghasilkan parameter RootType di dalamnya, membuat pengontrol Navigasi awal di Storyboard dan membungkus seluruh skrip di dalamnya.

Seperti yang Anda lihat, di sini koordinator memiliki 2 properti: navigationController; RootViewController dari RootType kami bersifat umum. Selama inisialisasi, kami meneruskannya bukan dengan navigationController tertentu, tetapi Storyboard, dari mana Navigasi root kami dan pengontrol pertama didapat. Dengan cara ini kita bahkan tidak perlu memanggil metode Mulai. Artinya, Anda membuat koordinator, ia segera memiliki Navigasi, dan segera memiliki Root. Anda dapat menunjukkan Navigasi secara modal, atau mengambil Root dan mendorong ke navigasi yang ada dan terus bekerja.
UserEditCoordinator kami dalam hal ini hanya akan menjadi typealias, menggantikan jenis RootViewController di parameter generik.
Transfer data skrip kembali
Mari kita bicara tentang memecahkan masalah terakhir, yang saya uraikan di awal. Ini adalah transfer data kembali ke skrip.

Pertimbangkan skenario yang sama untuk memilih kota, tetapi sekarang akan dimungkinkan untuk memilih bukan satu kota, tetapi beberapa. Untuk menunjukkan kepada pengguna bahwa ia telah memilih beberapa kota di wilayah yang sama, kami akan menunjukkan di layar dengan daftar wilayah sejumlah kecil di sebelah nama wilayah, yang menunjukkan jumlah kota yang dipilih di wilayah ini.
Ternyata aksi pada satu kontroler (pada yang ketiga) harus mengarah pada perubahan penampilan beberapa lainnya sekaligus. Artinya, di pertama kita harus menunjukkan di sel dengan kota, dan di kedua kita harus memperbarui semua angka di wilayah yang dipilih.
Koordinator menyederhanakan tugas ini dengan mentransfer data kembali ke skrip - ini sekarang tugas yang sederhana seperti mentransfer data ke depan sesuai dengan skrip.
Apa yang sedang terjadi di sini? Pengguna memilih kota. Pesan ini dikirim ke koordinator. Koordinator, seperti yang telah saya tunjukkan dalam demo, melewati seluruh tumpukan navigasi dan mengirimkan data yang diperbarui ke semua pihak yang berkepentingan. Dengan demikian, ViewController dapat memperbarui Lihat mereka dengan data ini.
Refactoring Kode yang Ada
Bagaimana cara memperbaiki kode yang ada jika Anda ingin menanamkan pendekatan ini dalam aplikasi yang sudah ada yang memiliki MVc, MVVm atau MVp?

Anda memiliki banyak ViewController. Hal pertama yang harus dilakukan adalah membaginya ke dalam skenario di mana mereka berpartisipasi. Dalam contoh kami, ada 3 skenario: otorisasi, pengeditan profil, rekaman.

Kami sekarang membungkus setiap skenario di dalam koordinator kami. Kita seharusnya dapat, pada kenyataannya, memulai skrip-skrip ini dari mana saja dalam aplikasi kita. Ini harus fleksibel -
koordinator harus sepenuhnya mandiri .
Pendekatan pengembangan ini memberikan kenyamanan tambahan. Terdiri dari fakta bahwa jika Anda saat ini bekerja dengan skenario tertentu, Anda tidak perlu mengkliknya setiap kali Anda memulainya. Anda dapat memulainya dengan cepat di awal, mengedit sesuatu di dalamnya, dan kemudian menghapus awal sementara ini.
Setelah kami memutuskan koordinator kami, kami perlu menentukan skenario mana yang bisa mengarah ke permulaan yang lain, dan membuat pohon dari skenario ini.

Dalam kasus kami, struktur pohonnya sederhana: LoginCoordinator dapat memulai koordinator pengeditan profil. Di sini, hampir semuanya jatuh pada tempatnya, tetapi detail yang sangat penting tetap ada - skema kami tidak memiliki titik masuk.

Titik masuk ini akan menjadi koordinator khusus -
ApplicationCoordinator . Itu dibuat dan dimulai oleh
AppDelegate , dan kemudian sudah mengendalikan logika di tingkat aplikasi, yaitu, koordinator mana yang mulai sekarang.
Kami hanya melihat sirkuit yang sangat mirip, hanya saja ViewController bukan koordinator, dan kami membuatnya sehingga ViewController tidak tahu apa-apa tentang satu sama lain dan tidak saling mengirimkan data. Pada prinsipnya, hal yang sama dapat dilakukan dengan koordinator. Kita dapat menetapkan Input (metode Mulai) dan Output tertentu (panggilan balik onFinish) di dalamnya.
Koordinator menjadi mandiri, dapat digunakan kembali, dan mudah diuji . Koordinator tidak lagi saling mengenal dan berkomunikasi, misalnya, hanya dengan ApplicationCoordinator.
Anda perlu berhati-hati, karena jika aplikasi Anda memiliki cukup skrip ini, maka ApplicationCoordinator dapat berubah menjadi objek dewa besar, ia akan tahu tentang semua skrip yang ada - ini juga tidak terlalu keren. Di sini kita harus sudah melihat - mungkin membagi koordinator menjadi sub-koordinator, yaitu, memikirkan arsitektur sedemikian rupa sehingga objek-objek ini tidak tumbuh ke ukuran luar biasa.
Meskipun ukuran tidak selalu menjadi alasan untuk refactoring .
Mulai dari mana
Saya menyarankan mulai dari bawah ke atas - pertama mengimplementasikan skrip individual.

Sebagai solusinya, mereka dapat dimulai di dalam UIViewController. Artinya, selama Anda tidak memiliki Root atau koordinator lain, Anda dapat membuat satu koordinator dan, sebagai solusi sementara, mulai dari UIViewController, simpan secara lokal di properti (seperti Koordinator selanjutnya adalah di atas). Ketika suatu peristiwa terjadi, Anda, seperti yang saya tunjukkan dalam demo, membuat properti lokal, meletakkan koordinator di sana dan memanggil metode Mulai di atasnya. Semuanya sangat sederhana.
Kemudian, ketika semua koordinator telah melakukannya, awal dari satu di dalam yang lain terlihat persis sama. Apakah Anda memiliki properti lokal atau beberapa jenis dependensi seperti koordinator, Anda meletakkan semua ini di sana sehingga tidak lari, dan memanggil metode Mulai.
Ringkasan
- Layar dan skrip independen yang tidak tahu apa-apa tentang satu sama lain tidak saling berkomunikasi. Kami berusaha mencapai ini.
- Mudah untuk mengubah urutan layar dalam aplikasi tanpa mengubah kode layar. Jika semuanya dilakukan sebagaimana mestinya, satu-satunya hal yang harus berubah dalam aplikasi ketika skrip berubah bukan kode layar, tetapi kode koordinator.
- Transfer data yang disederhanakan antara layar dan tugas-tugas lain yang menyiratkan koneksi antara layar.
- — , .
AppsConf 2018 8 9 — ! ( ) . — iOS Android, , , , .