Ketergantungan injeksi dengan DITranquillity

Dependency Injection adalah pola yang cukup populer yang memungkinkan Anda untuk mengkonfigurasi sistem secara fleksibel dan secara benar membangun ketergantungan komponen-komponen sistem ini satu sama lain. Berkat pengetikan, Swift memungkinkan Anda untuk menggunakan kerangka kerja yang nyaman yang dengannya Anda dapat dengan singkat menggambarkan grafik dependensi. Hari ini saya ingin berbicara sedikit tentang salah satu kerangka kerja ini - DITranquillity .


Tutorial ini akan membahas fitur-fitur perpustakaan berikut:


  • Ketik Registrasi
  • Penyebaran Inisialisasi
  • Menanamkan dalam variabel
  • Ketergantungan siklik komponen
  • Menggunakan perpustakaan dengan UIStoryboard

Deskripsi Komponen


Aplikasi ini akan terdiri dari komponen utama berikut: ViewController , Router , Presenter , Networking - ini adalah komponen yang cukup umum di aplikasi iOS apa pun.


Struktur komponen

ViewController dan Router akan saling diperkenalkan secara siklis.


Persiapan


Pertama, buat Aplikasi Tampilan Tunggal dalam Xcode, tambahkan DITranquillity menggunakan CocoaPods . Buat hierarki file yang diperlukan, lalu tambahkan pengontrol kedua ke Main.storyboard dan hubungkan menggunakan StoryboardSegue . Akibatnya, struktur file berikut harus diperoleh:


Struktur file

Buat dependensi di kelas-kelas sebagai berikut:


Deklarasi Komponen
 protocol Presenter: class { func getCounter(completion: @escaping (Int) -> Void) } class MyPresenter: Presenter { private let networking: Networking init(networking: Networking) { self.networking = networking } func getCounter(completion: @escaping (Int) -> Void) { // Implementation } } 

 protocol Networking: class { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) } class MyNetworking: Networking { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) { // Implementation } } 

 protocol Router: class { func presentNewController() } class MyRouter: Router { unowned let viewController: ViewController init(viewController: ViewController) { self.viewController = viewController } func presentNewController() { // Implementation } } 

 class ViewController: UIViewController { var presenter: Presenter! var router: Router! } 

Keterbatasan


Tidak seperti kelas lain, ViewController tidak dibuat oleh kami, tetapi oleh perpustakaan UIKit di dalam implementasi UIStoryboard.instantiateViewController , oleh karena itu, menggunakan storyboard, kami tidak dapat menyuntikkan dependensi ke dalam pewaris UIViewController menggunakan inisialisasi. Begitu pula dengan ahli waris UIView dan UITableViewCell .


Perhatikan bahwa objek yang tersembunyi di balik protokol tertanam di semua kelas. Ini adalah salah satu tugas utama penerapan dependensi - untuk membuat dependensi bukan pada implementasi, tetapi pada antarmuka. Ini akan membantu di masa depan untuk menyediakan implementasi protokol yang berbeda untuk menggunakan kembali atau menguji komponen.


Injeksi Ketergantungan


Setelah semua komponen sistem dibuat, kami melanjutkan ke koneksi objek di antara mereka sendiri. Dalam DITranquillity, titik awalnya adalah DIContainer , yang menambahkan pendaftaran menggunakan metode container.register(...) . Untuk memisahkan ketergantungan menjadi beberapa bagian, DIFramework dan DIPart , yang harus diimplementasikan. Untuk kenyamanan, kami hanya akan membuat satu kelas ApplicationDependency , yang akan mengimplementasikan DIFramework dan akan berfungsi sebagai tempat pendaftaran untuk semua dependensi. Antarmuka DIFramework mengharuskan Anda untuk menerapkan hanya satu metode - load(container:) .


 class ApplicationDependency: DIFramework { static func load(container: DIContainer) { // registrations will be placed here } } 

Mari kita mulai dengan pendaftaran paling sederhana yang tidak memiliki dependensi - MyNetworking


 container.register(MyNetworking.init) 

Registrasi ini menggunakan implementasi melalui penginisialisasi. Terlepas dari kenyataan bahwa komponen itu sendiri tidak memiliki dependensi, penginisialisasi harus disediakan untuk menjelaskan kepada perpustakaan cara membuat komponen.


Demikian pula, daftarkan MyPresenter dan MyRouter .


 container.register1(MyPresenter.init) container.register1(MyRouter.init) 

Catatan: Perhatikan bahwa ini bukan register yang digunakan, tetapi register1 . Sayangnya, ini perlu untuk menunjukkan apakah objek memiliki satu dan hanya satu ketergantungan pada penginisialisasi. Artinya, jika ada 0 atau dua atau lebih dependensi, Anda hanya perlu menggunakan register . Pembatasan ini adalah bug dari Swift versi 4.0 dan lebih tinggi.


Sudah waktunya untuk mendaftarkan ViewController kami. Itu menyuntikkan objek tidak melalui penginisialisasi, tetapi langsung ke dalam variabel, sehingga deskripsi registrasi akan menjadi sedikit lebih.


 container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter) 

Sintaks dari form \.presenter adalah SwiftKeyPath, berkat itu memungkinkan untuk mengimplementasikan dependensi secara ringkas. Karena Router dan ViewController bergantung satu sama lain, Anda harus secara eksplisit menunjukkan ini menggunakan cycle: true . Perpustakaan itu sendiri dapat menyelesaikan dependensi ini tanpa indikasi yang jelas, tetapi persyaratan ini diperkenalkan sehingga orang yang membaca grafik segera memahami bahwa ada siklus dalam rantai dependensi. Juga perhatikan bahwa BUKAN ViewController.init , tetapi ViewController.self . Ini dijelaskan di atas di bagian Keterbatasan .


Anda juga perlu mendaftarkan UIStoryboard menggunakan metode khusus.


 container.registerStoryboard(name: "Main") 

Sekarang kami telah menggambarkan seluruh grafik dependensi untuk satu layar. Tetapi belum ada akses ke grafik ini. Anda harus membuat DIContainer yang memungkinkan Anda mengakses objek di dalamnya.


 static let container: DIContainer = { let container = DIContainer() // 1 container.append(framework: ApplicationDependency.self) // 2 assert(container.validate(checkGraphCycles: true)) // 3 return container }() 

  1. Inisialisasi wadah
  2. Tambahkan deskripsi grafik ke dalamnya.
  3. Kami memeriksa bahwa kami melakukan semuanya dengan benar. Jika kesalahan dibuat, aplikasi tidak akan crash saat menyelesaikan dependensi, tetapi segera saat membuat grafik

Maka Anda perlu menjadikan wadah sebagai titik awal aplikasi. Untuk melakukan ini, AppDelegate menerapkan metode didFinishLaunchingWithOptions di didFinishLaunchingWithOptions alih-alih menetapkan Main.storyboard sebagai titik peluncuran di pengaturan proyek.


 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) let storyboard: UIStoryboard = ApplicationDependency.container.resolve() window?.rootViewController = storyboard.instantiateInitialViewController() window?.makeKeyAndVisible() return true } 

Luncurkan


Pada awal pertama, akan terjadi penurunan dan validasi akan gagal karena alasan berikut:


  • Wadah tidak akan menemukan jenis Router , Presenter , Networking , karena kami hanya mendaftarkan objek. Jika kita ingin memberikan akses bukan ke implementasi, tetapi ke antarmuka, kita harus secara eksplisit menentukan antarmuka
  • Wadah tidak mengerti bagaimana menyelesaikan ketergantungan siklik, karena itu perlu untuk secara eksplisit menunjukkan objek mana yang tidak harus dibuat ulang setiap kali grafik diselesaikan.

Sangat mudah untuk memperbaiki kesalahan pertama - ada metode khusus yang memungkinkan Anda menentukan protokol mana yang tersedia dalam wadah.


 container.register(MyNetworking.init) .as(check: Networking.self) {$0} 

Menjelaskan registrasi sebagai berikut, kita katakan: objek MyNetworking dapat diakses melalui protokol Networking . Ini harus dilakukan untuk semua objek yang disembunyikan di bawah protokol. {$0} menambahkan untuk pemeriksaan jenis yang benar oleh kompiler.


Kesalahan kedua sedikit lebih rumit. Adalah perlu untuk menggunakan scope disebut, yang menggambarkan seberapa sering objek dibuat dan seberapa banyak ia hidup. Untuk setiap pendaftaran yang berpartisipasi dalam dependensi melingkar, Anda harus menentukan ruang scope sama dengan objectGraph . Ini akan memperjelas wadah bahwa selama penyelesaian diperlukan untuk menggunakan kembali objek yang dibuat, daripada membuat setiap waktu. Jadi, ternyata:


 container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter) .lifetime(.objectGraph) container.register1(MyRouter.init) .as(check: Router.self) {$0} .lifetime(.objectGraph) 

Setelah memulai ulang, wadah berhasil lulus validasi dan ViewController kami terbuka dengan dependensi yang dibuat. Anda dapat meletakkan breakpoint di viewDidLoad dan pastikan.


Transisi antar layar


Selanjutnya, buat dua kelas kecil, SecondViewController dan SecondPresenter , tambahkan SecondViewController ke storyboard dan buat Segue antara mereka dengan pengidentifikasi "RouteToSecond" , yang memungkinkan Anda untuk membuka pengontrol kedua dari yang pertama.


Tambahkan dua pendaftaran lagi ke ApplicationDependency kami untuk masing-masing kelas baru:


 container.register(SecondViewController.self) .injection(\.secondPresenter) container.register(SecondPresenter.init) 

.as perlu menentukan .as , karena kami tidak menyembunyikan SecondPresenter belakang protokol, tetapi menggunakan implementasinya secara langsung. Kemudian, dalam metode viewDidAppear pengontrol pertama, kami memanggil performSegue(withIdentifier: "RouteToSecond", sender: self) , start, controller kedua terbuka, di mana dependensi secondPresenter harus ditempelkan. Seperti yang Anda lihat, wadah melihat pembuatan pengontrol kedua dari UIStoryboard dan berhasil meletakkan dependensi.


Kesimpulan


Pustaka ini memungkinkan Anda bekerja dengan nyaman dengan dependensi siklik, storyboard, dan sepenuhnya menggunakan inferensi tipe otomatis di Swift, yang memberikan sintaks yang sangat singkat dan fleksibel untuk menggambarkan grafik dependensi.


Referensi


Kode sampel lengkap di perpustakaan github


DITranquillity pada github


Artikel bahasa inggris

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


All Articles