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.
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:
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) {
protocol Networking: class { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) } class MyNetworking: Networking { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) {
protocol Router: class { func presentNewController() } class MyRouter: Router { unowned let viewController: ViewController init(viewController: ViewController) { self.viewController = viewController } func presentNewController() {
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) {
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()
- Inisialisasi wadah
- Tambahkan deskripsi grafik ke dalamnya.
- 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