Perselisihan yang tenang dan tenang

ikon perpustakaan Tiga tahun lalu, saya menulis artikel tentang perpustakaan DI untuk bahasa Swift. Sejak saat itu, perpustakaan telah banyak berubah dan menjadi yang terbaik dari pesaing yang layak untuk Swinject, melampaui itu dalam banyak hal. Artikel ini dikhususkan untuk kemampuan perpustakaan, tetapi juga memiliki pertimbangan teoretis. Jadi, yang tertarik dengan topik DI, DIP, IoC, atau yang membuat pilihan antara Swinject dan Swinject, saya meminta potongan:


Apa DIP, IoC dan apa yang dimakannya?


Teori DIP dan IoC


Teori adalah salah satu komponen terpenting dalam pemrograman. Ya, Anda dapat menulis kode tanpa pendidikan, tetapi meskipun demikian, programmer terus membaca artikel, tertarik pada berbagai praktik, dll. Yaitu, satu atau lain cara saya mendapatkan pengetahuan teoritis untuk mempraktikkannya.

Salah satu topik yang ingin ditanyakan orang adalah SOLID . Tidak ada artikel tentang dia sama sekali, jangan khawatir. Tapi kami butuh satu surat, karena terkait erat dengan perpustakaan saya. Ini adalah huruf `D` - Prinsip Ketergantungan Pembalikan.

Prinsip Ketergantungan Inversi menyatakan:

  • Modul tingkat atas tidak harus bergantung pada modul tingkat bawah. Kedua jenis modul harus bergantung pada abstraksi.
  • Abstraksi tidak harus bergantung pada detail. Rinciannya harus bergantung pada abstraksi.

Banyak orang secara keliru menganggap bahwa jika mereka menggunakan protokol / antarmuka, maka mereka secara otomatis mematuhi prinsip ini, tetapi ini tidak sepenuhnya benar.

Pernyataan pertama mengatakan sesuatu tentang dependensi antar modul - modul harus bergantung pada abstraksi. Tunggu, apa itu abstraksi? - Lebih baik bertanya pada diri sendiri bukan apa itu abstraksi, tapi apa abstraksi itu? Artinya, Anda perlu memahami apa prosesnya, dan hasil dari proses ini akan menjadi abstraksi. Abstraksi adalah gangguan dalam proses kognisi dari pihak-pihak yang tidak penting, properti, hubungan untuk menyoroti tanda-tanda penting dan teratur.

Objek yang sama, tergantung pada tujuannya, dapat memiliki abstraksi yang berbeda. Misalnya, mesin dari sudut pandang pemilik memiliki sifat-sifat penting berikut: warna, keanggunan, kenyamanan. Tapi dari sudut pandang mekanik, semuanya agak berbeda: merek, model, modifikasi, jarak tempuh, partisipasi dalam suatu kecelakaan. Dua abstraksi berbeda untuk satu objek baru saja dinamai - mesin.

Perhatikan bahwa dalam Swift biasanya menggunakan protokol untuk abstraksi, tetapi ini bukan keharusan. Tidak ada yang mengganggu untuk membuat kelas, mengalokasikan satu set metode publik dari itu, dan meninggalkan detail implementasi pribadi. Dalam hal abstraksi, tidak ada yang rusak. Kita harus ingat tesis penting - “abstraksi tidak terikat dengan bahasa” - ini adalah proses yang terjadi terus-menerus di kepala kita, dan bagaimana ini ditransfer ke kode tidak begitu penting. Di sini kita juga bisa menyebutkan enkapsulasi , sebagai contoh dari apa yang terkait dengan bahasa. Setiap bahasa memiliki caranya sendiri untuk menyediakannya. Di Swift, ini adalah kelas, bidang akses, dan protokol; pada antarmuka Obj-C, protokol dan pemisahan file h dan m.

pernyataan kedua lebih menarik, karena diabaikan atau disalahpahami. Ini berbicara tentang interaksi abstraksi dengan detail, dan apa detailnya? Ada kesalahpahaman bahwa rinciannya adalah kelas yang mengimplementasikan protokol - ya ini benar, tetapi tidak lengkap. Anda perlu memahami bahwa rinciannya tidak terkait dengan bahasa pemrograman - bahasa C tidak memiliki protokol atau kelas, tetapi prinsip ini juga bertindak berdasarkan hal itu. Sulit bagi saya untuk menjelaskan secara teoritis apa hasil tangkapannya, jadi saya akan memberikan dua contoh, dan kemudian mencoba membuktikan mengapa contoh kedua lebih tepat.

Misalkan ada mobil kelas dan mesin kelas. Kebetulan kami harus menghubungkan mereka - mesin berisi mesin. Kami, sebagai programmer yang kompeten, memilih mesin protokol, mengimplementasikan protokol dan meneruskan implementasi protokol ke kelas mesin. Semuanya tampak baik dan benar - sekarang Anda dapat dengan mudah mengganti implementasi mesin dan tidak berpikir bahwa ada sesuatu yang akan rusak. Selanjutnya, mekanik mesin ditambahkan ke sirkuit. Dia tertarik pada karakteristik mesin yang sama sekali berbeda dari mobil. Kami memperluas protokol dan sekarang berisi serangkaian fitur yang lebih besar daripada awalnya. Kisah ini diulangi untuk pemilik mobil, untuk pabrik yang memproduksi mesin, dll.

Tidak ada inversi

Tapi di mana kesalahan dalam berpikir? Masalahnya adalah bahwa koneksi yang dijelaskan, terlepas dari ketersediaan protokol, sebenarnya adalah "detail" - "detail". Lebih tepatnya, dalam nama apa dan di mana protokol berada mesin.

Sekarang pertimbangkan opsi lain yang benar .

Seperti sebelumnya, ada dua kelas - mesin dan mobil. Seperti sebelumnya, mereka harus terhubung. Tapi sekarang kami mengumumkan protokol "Mesin Mobil" atau "Jantung Mobil". Kami menempatkan di dalamnya hanya karakteristik yang dibutuhkan mobil dari mesin. Dan kami menempatkan protokol tidak di sebelah implementasi "mesin", tetapi di samping mesin. Lebih lanjut, jika kita membutuhkan mekanik, kita perlu membuat protokol lain dan mengimplementasikannya di mesin. Tampaknya tidak ada yang berubah, tetapi pendekatannya sangat berbeda - pertanyaannya bukan pada namanya, tetapi pada siapa protokolnya dan apa protokolnya - "abstraksi" atau "detail".

Inversi adalah

Sekarang mari kita menggambar analogi dengan kasus lain, karena argumen ini mungkin tidak jelas.

Ada backend dan beberapa fungsionalitas diperlukan darinya. Backend memberi kami metode besar yang berisi banyak data, dan mengatakan - "Anda membutuhkan 3 bidang ini dari 1000"

Sedikit cerita
Banyak yang dapat mengatakan bahwa ini tidak terjadi. Dan mereka akan relatif benar - kebetulan backend ditulis secara terpisah untuk aplikasi mobile. Kebetulan saya bekerja untuk sebuah perusahaan di mana backend adalah layanan dengan sejarah 10 tahun yang, antara lain, terkait dengan API negara. Untuk banyak alasan, bukan kebiasaan bagi perusahaan untuk menulis metode terpisah untuk ponsel, dan saya harus menggunakan apa yang ada. Dan ada satu metode luar biasa dengan sekitar seratus parameter di root, dan beberapa di antaranya adalah kamus bersarang. Sekarang bayangkan 100 parameter, 20% di antaranya memiliki parameter bersarang, dan di dalam masing-masing parameter bersarang ada 20-30 parameter lain yang semuanya bersarang sama. Saya tidak ingat persis, tetapi jumlah parameter melebihi 800 untuk objek sederhana, dan untuk yang kompleks bisa lebih tinggi dari 1000.

Kedengarannya tidak terlalu bagus, bukan? Biasanya backend menulis metode untuk tugas-tugas spesifik untuk frontend, dan frontend adalah pelanggan / pengguna metode ini. Hmm ... Tetapi jika Anda memikirkannya, backend adalah mesin, dan frontend adalah mobil - mesin membutuhkan beberapa karakteristik mesin, dan bukan mesin perlu memberikan karakteristik untuk mobil. Jadi mengapa, meskipun demikian, kami terus menulis Engine protokol dan menempatkannya lebih dekat dengan implementasi mesin, dan bukan mesin? Ini semua tentang skala - di sebagian besar program iOS sangat jarang harus memperluas fungsionalitas sehingga solusi seperti itu menjadi masalah.

Lalu apa itu DI


Ada substitusi konsep - DI bukan singkatan untuk DIP, tetapi singkatan yang sama sekali berbeda, meskipun faktanya sangat bersinggungan dengan DIP. DI adalah injeksi ketergantungan atau Injeksi Ketergantungan, bukan Inversi. Pembalikan berbicara tentang bagaimana kelas dan protokol harus berinteraksi satu sama lain, dan implementasi memberi tahu Anda dari mana mendapatkannya. Secara umum, Anda dapat mengimplementasikannya dengan berbagai cara - mulai dari mana dependensi datang: konstruktor, properti, metode; diakhiri dengan mereka yang membuatnya dan seberapa otomatis proses ini. Pendekatannya berbeda tetapi, menurut saya, yang paling nyaman adalah wadah untuk injeksi ketergantungan. Singkatnya, seluruh maknanya bermuara pada aturan sederhana: Kami memberi tahu wadah di mana dan bagaimana menerapkannya dan setelah itu semuanya dilaksanakan secara independen. Pendekatan ini sesuai dengan "implementasi nyata dari dependensi" - ini adalah ketika kelas-kelas di mana dependensi tertanam tidak tahu apa-apa tentang bagaimana ini terjadi, yaitu mereka pasif.

Dalam banyak bahasa, pendekatan berikut digunakan untuk implementasi ini: Dalam kelas / file individual, aturan implementasi dijelaskan menggunakan sintaksis bahasa, setelah itu mereka dikompilasi dan diimplementasikan secara otomatis. Tidak ada keajaiban - tidak ada yang terjadi secara otomatis, hanya perpustakaan yang terintegrasi erat dengan sarana dasar bahasa, dan membebani metode pembuatan. Jadi untuk Swift / Obj-C secara umum diterima bahwa titik awalnya adalah UIViewController, dan perpustakaan dapat dengan mudah mengintegrasikan diri ke dalam ViewController yang dibuat dari Storyboard. Benar, jika Anda tidak menggunakan Storyboard, Anda harus melakukan sebagian pekerjaan dengan pena.

Oh ya, saya hampir lupa - jawaban untuk pertanyaan utama, "mengapa kita membutuhkan ini?" Tidak diragukan lagi, Anda dapat merawat injeksi ketergantungan sendiri, meresepkan semuanya dengan pena. Tetapi masalah muncul ketika grafik menjadi besar - Anda harus menyebutkan banyak koneksi antar kelas, kode mulai tumbuh sangat banyak. Oleh karena itu, perpustakaan yang secara otomatis menerapkan dependensi secara rekursif (dan bahkan secara siklikal) merawat diri mereka sendiri dan, sebagai bonus, mengontrol masa hidup mereka. Artinya, perpustakaan tidak melakukan apa pun di luar yang alami - itu hanya menyederhanakan kehidupan pengembang. Benar, jangan berpikir bahwa Anda dapat menulis perpustakaan seperti itu dalam sehari - itu adalah satu hal untuk menulis dengan pena semua dependensi untuk kasus tertentu, itu hal lain untuk mengajar komputer untuk menerapkan secara universal dan benar.

Sejarah perpustakaan


Kisah itu tidak akan lengkap jika saya tidak menceritakannya secara singkat. Jika Anda mengikuti perpustakaan dari versi beta, itu tidak akan begitu menarik bagi Anda, tetapi bagi mereka yang melihatnya untuk pertama kali, saya pikir layak untuk memahami bagaimana itu muncul dan apa tujuan yang diikuti penulis (yaitu, saya).
Perpustakaan adalah proyek kedua saya, yang saya putuskan, untuk keperluan pendidikan mandiri, untuk menulis dalam bahasa Swift. Sebelumnya saya berhasil menulis logger, tetapi tidak mengunggahnya ke domain publik - ini lebih baik dan lebih baik.

Tetapi dengan DI, ceritanya lebih menarik. Ketika saya mulai melakukannya, saya hanya dapat menemukan satu perpustakaan di Swift - Swinject. Pada saat itu, ia memiliki 500 bintang dan bug yang siklusnya biasanya tidak diproses. Saya melihat semua ini dan ... Perilaku saya paling baik dijelaskan dengan frasa favorit saya "Dan kemudian Ostap menderita" - Saya mempelajari 5-6 bahasa, melihat apa yang ada dalam bahasa-bahasa ini, membaca artikel tentang topik ini dan menyadari bahwa itu bisa dilakukan dengan lebih baik. Dan sekarang, setelah hampir tiga tahun, saya dapat mengatakan dengan keyakinan bahwa tujuan telah tercapai, saat ini DITranquillity adalah yang terbaik dalam pandangan dunia saya.

Mari kita pahami apa itu perpustakaan DI yang baik:

  • Ini harus menyediakan semua implementasi dasar: konstruktor, properti, metode
  • Seharusnya tidak memengaruhi kode bisnis.
  • Dia harus dengan jelas menggambarkan apa yang salah.
  • Dia harus memahami terlebih dahulu di mana ada kesalahan, bukan saat runtime.
  • Itu harus diintegrasikan dengan alat dasar (Storyboard)
  • Seharusnya memiliki sintaksis ringkas dan ringkas.
  • Dia harus melakukan segalanya dengan cepat dan efisien.
  • (Opsional) Itu harus hirarkis

Prinsip-prinsip inilah yang saya coba patuhi sepanjang pengembangan perpustakaan.

Fitur dan Manfaat Perpustakaan


Pertama, tautan ke repositori: github.com/ivlevAstef/DITranquillity

Keuntungan kompetitif utama, yang cukup penting bagi saya, adalah bahwa perpustakaan berbicara tentang kesalahan startup. Setelah memulai aplikasi dan memanggil fungsi yang diinginkan, semua masalah, baik yang sudah ada maupun yang potensial, akan dilaporkan. Inilah makna nama "tenang" perpustakaan - pada kenyataannya, setelah memulai program, perpustakaan menjamin bahwa semua dependensi yang diperlukan akan ada dan tidak ada siklus yang tidak dapat diselesaikan. Di tempat-tempat di mana ada ambiguitas, perpustakaan akan memperingatkan bahwa mungkin ada masalah potensial.

Kedengarannya baik bagi saya. Tidak ada crash selama pelaksanaan program, jika programmer lupa sesuatu, maka ini akan segera dilaporkan.

Fungsi log digunakan untuk menggambarkan masalah, yang sangat saya sarankan untuk digunakan. Logging memiliki 4 level: kesalahan, peringatan, info, verbose. Tiga yang pertama cukup penting. Yang terakhir tidak begitu penting - ia menulis semua yang terjadi - objek mana yang terdaftar, objek mana yang mulai diperkenalkan, objek apa yang dibuat, dll.

Tapi ini tidak semua perpustakaan menawarkan:

  • Keamanan ulir penuh - operasi apa pun dapat dilakukan dari utas apa pun dan semuanya akan berfungsi. Kebanyakan orang tidak membutuhkan ini, jadi dalam hal keamanan benang, pekerjaan dilakukan untuk mengoptimalkan kecepatan eksekusi. Tetapi perpustakaan pesaing, terlepas dari janji-janji, jatuh jika Anda mulai mendaftar dan menerima objek pada saat yang sama
  • Kecepatan eksekusi cepat. Pada perangkat nyata, DITranquillity dua kali lebih cepat dari pesaingnya. Benar pada simulator, kecepatan eksekusi hampir setara. Tautan Uji
  • Ukuran kecil - perpustakaan beratnya kurang dari Swinject + SwinjectStoryboad + SwinjectAutoregistration, tetapi melampaui bundel ini dalam kemampuan
  • Catatan singkat, ringkas, meskipun membuat ketagihan
  • Hierarki. Untuk proyek-proyek besar, yang terdiri dari banyak modul, ini merupakan nilai tambah yang sangat besar, karena perpustakaan dapat menemukan kelas yang diperlukan dengan jarak dari modul saat ini. Artinya, jika Anda memiliki implementasi sendiri dari satu protokol di setiap modul, maka di setiap modul Anda akan mendapatkan implementasi yang diinginkan tanpa melakukan upaya apa pun

Demonstrasi


Jadi mari kita mulai. Sebagai proyek terakhir kali akan dipertimbangkan: SampleHabr . Saya secara khusus tidak mulai mengubah contoh - sehingga Anda dapat membandingkan bagaimana semuanya telah berubah. Dan contohnya menampilkan banyak fitur perpustakaan.

Untuk jaga-jaga, agar tidak ada kesalahpahaman, karena proyek ini dipamerkan, ia menggunakan banyak fitur. Tapi tidak ada yang mengganggu untuk menggunakan perpustakaan dengan cara yang disederhanakan - diunduh, dibuat wadah, terdaftar beberapa kelas, gunakan wadah.

Pertama, kita perlu membuat kerangka kerja (opsional):

public class AppFramework: DIFramework { //   public static func load(container: DIContainer) { //     } } 

Dan di awal program, buat wadah Anda sendiri, dengan tambahan kerangka kerja ini:

 let container = DIContainer() //   container.append(framework: AppFramework.self) //     . //          ifdef DEBUG      ,         ,     . if !container.validate() { fatalError() } 

Storyboard


Selanjutnya, Anda perlu membuat layar dasar. Biasanya Storyboard digunakan untuk ini, dan dalam contoh ini saya akan menggunakannya, tetapi tidak ada yang mengganggu untuk menggunakan UIViewControllers.

Untuk memulainya, kita perlu mendaftarkan Storyboard. Untuk melakukan ini, buat "bagian" (opsional - Anda dapat menulis semua kode dalam kerangka kerja) dengan Storyboard terdaftar di dalamnya:

 import DITranquillity class AppPart: DIPart { static func load(container: DIContainer) { container.registerStoryboard(name: "Main", bundle: nil) .lifetime(.single) //   -    . } } 


Dan tambahkan bagian ke AppFramework:
 container.append(part: AppPart.self) 

Seperti yang Anda lihat, perpustakaan memiliki sintaks yang nyaman untuk mendaftar Storyboard, dan saya sangat merekomendasikan menggunakannya. Pada prinsipnya, Anda dapat menulis kode yang setara tanpa metode ini, tetapi akan lebih besar dan tidak akan dapat mendukung StoryboardReferences. Artinya, Storyboard ini tidak akan berfungsi dari yang lain.

Sekarang satu-satunya yang tersisa adalah membuat Storyboard dan menampilkan layar mulai. Ini dilakukan di AppDelegate, setelah memeriksa wadah:

 window = UIWindow(frame: UIScreen.main.bounds) ///  Storyboard let storyboard: UIStoryboard = container.resolve(name: "Main") window!.rootViewController = storyboard.instantiateInitialViewController() window!.makeKeyAndVisible() 

Membuat Storyboard menggunakan perpustakaan tidak jauh lebih rumit dari biasanya. Dalam contoh ini, namanya dapat terlewatkan, karena kami hanya memiliki satu Storyboard - perpustakaan akan menduga bahwa Anda sudah memikirkannya. Tetapi dalam beberapa proyek ada banyak Storyboard, jadi jangan lewatkan namanya lagi.

Presenter dan ViewController


Pergi ke layar itu sendiri. Kami tidak akan memuat proyek dengan arsitektur yang kompleks, tetapi kami akan menggunakan MVP biasa. Selain itu, saya sangat malas sehingga saya tidak akan membuat protokol untuk presenter. Protokol akan sedikit lebih lambat untuk kelas lain, di sini penting untuk menunjukkan cara mendaftar dan menautkan Presenter dan ViewController.

Untuk melakukan ini, tambahkan kode berikut ke AppPart:

 container.register(YourPresenter.init) container.register(YourViewController.self) .injection(\.presenter) //   

Tiga baris ini akan memungkinkan kita untuk mendaftar dua kelas, dan membangun koneksi di antara mereka.

Orang-orang yang penasaran mungkin bertanya-tanya - mengapa sintaksis yang dimiliki Swinject di perpustakaan terpisah dijadikan yang utama dalam proyek ini? Jawabannya terletak pada sasaran - berkat sintaks ini, perpustakaan menyimpan semua tautan di muka, daripada menghitungnya saat runtime. Sintaks ini memberi Anda akses ke banyak fitur yang tidak tersedia untuk perpustakaan lain.

Kami memulai aplikasi, dan semuanya berfungsi, semua kelas dibuat.

Data


Nah, sekarang kita perlu menambahkan kelas dan protokol untuk menerima data dari server:

 public protocol Server { func get(method: String) -> Data? } class ServerImpl: Server { init(domain: String) { ... } func get(method: String) -> Data? { ... } } 

Dan untuk kecantikan, kami akan membuat kelas DI ServerPart terpisah untuk server, tempat kami mendaftarkannya. Biarkan saya mengingatkan Anda bahwa ini tidak perlu dan dapat didaftarkan langsung dalam wadah, tetapi kami tidak mencari cara mudah :)

 import DITranquillity class ServerPart: DIPart { static func load(container: DIContainer) { container.register{ ServerImpl(domain: "https://github.com/") } .as(check: Server.self){$0} .lifetime(.single) } } 

Dalam kode ini, semuanya tidak setransparan yang sebelumnya, dan membutuhkan klarifikasi. Pertama, di dalam register fungsi, kelas dibuat dengan melewati parameter.

Kedua, ada fungsi `as` - ia mengatakan bahwa kelas akan dapat diakses oleh tipe lain - protokol. Akhir yang aneh dari operasi ini dalam bentuk `{$ 0}` adalah bagian dari nama `check:`. Artinya, kode ini memastikan bahwa ServerImpl adalah penerus Server. Tetapi ada sintaks lain: `as (Server.self)` yang akan melakukan hal yang sama, tetapi tanpa memeriksa. Untuk melihat apa yang akan dihasilkan oleh kompiler dalam kedua kasus, Anda dapat menghapus implementasi protokol.

Mungkin ada beberapa fungsi `as` - ini berarti jenisnya tersedia dengan nama-nama ini. Saya menarik perhatian Anda bahwa ini akan menjadi satu pendaftaran, yang berarti bahwa jika kelas adalah singleton, maka contoh yang sama akan tersedia untuk semua jenis yang ditentukan.

Pada prinsipnya, jika Anda ingin melindungi diri dari kemungkinan membuat kelas berdasarkan jenis implementasi, atau Anda belum terbiasa dengan sintaks ini, maka Anda dapat menulis:

 container.register{ ServerImpl(domain: "https://github.com/") as Server } 

Yang akan menjadi beberapa setara, tetapi tanpa kemampuan untuk menentukan beberapa tipe yang terpisah.

Sekarang Anda dapat mengimplementasikan server di Presenter, untuk ini kami akan memperbaiki Presenter sehingga menerima Server:

  class YourPresenter { init(server: Server) { ... } } 

Kami memulai program, dan jatuh pada fungsi `validasi` di AppDelegate, dengan pesan bahwa jenis` Server` tidak ditemukan, tetapi diperlukan oleh` YourPresenter`. Ada apa? Harap dicatat bahwa kesalahan terjadi pada awal pelaksanaan program, dan bukan post factum. Dan alasannya cukup sederhana - mereka lupa menambahkan `ServerPart` ke` AppFramework`:

 container.append(part: ServerPart.self) 

Kami mulai - semuanya bekerja.

Logger


Sebelum ini, ada seorang kenalan dengan peluang yang tidak terlalu mengesankan dan banyak dimiliki. Sekarang akan ada demonstrasi bahwa perpustakaan lain di Swift tidak tahu caranya.

Proyek terpisah dibuat di bawah penebang.

Pertama, mari kita pahami apa yang akan menjadi penebang. Untuk tujuan pendidikan, kami tidak akan melakukan sistem tipuan, sehingga logger adalah protokol dengan satu metode dan beberapa implementasi:

 public protocol Logger { func log(_ msg: String) } class ConsoleLogger: Logger { func log(_ msg: String) { ... } } class FileLogger: Logger { init(file: String) { ... } func log(_ msg: String) { ... } } class ServerLogger: Logger { init(server: String) { ... } func log(_ msg: String) { ... } } class MainLogger: Logger { init(loggers: [Logger]) { ... } func log(_ msg: String) { ... } } 

Total, kami memiliki:

  • Protokol publik
  • 3 implementasi logger berbeda, yang masing-masing menulis ke tempat yang berbeda
  • Satu logger pusat yang memanggil fungsi logging untuk semua orang

Proyek ini menciptakan `LoggerFramework` dan` LoggerPart`. Saya tidak akan menulis kode mereka, tetapi saya hanya akan menulis internal `LoggerPart`:

 container.register{ ConsoleLogger() } .as(Logger.self) .lifetime(.single) container.register{ FileLogger(file: "file.log") } .as(Logger.self) .lifetime(.single) container.register{ ServerLogger(server: "http://server.com/") } .as(Logger.self) .lifetime(.single) container.register{ MainLogger(loggers: many($0)) } .as(Logger.self) .default() .lifetime(.single) 

Kami telah melihat 3 pendaftaran pertama, dan yang terakhir menimbulkan pertanyaan.

Parameter dilewatkan ke input. Yang serupa sudah ditunjukkan ketika presenter dibuat, meskipun ada catatan disingkat - metode `init` hanya digunakan, tetapi tidak ada yang mengganggu untuk menulis seperti ini:

 container.register { YourPresenter(server: $0) } 

Jika ada beberapa parameter, maka seseorang dapat menggunakan `$ 1`,` $ 2`, `$ 3`, dll. sampai 16.

Tetapi parameter ini memanggil fungsi `many`. Dan di sini kesenangan dimulai. Ada dua pengubah `banyak` dan` tag` di perpustakaan.
Teks tersembunyi
Ada pengubah `arg` ketiga, tetapi tidak aman
Pengubah `many` mengatakan bahwa Anda harus mendapatkan semua objek yang sesuai dengan tipe yang diinginkan. Dalam hal ini, protokol Logger diharapkan, sehingga semua kelas yang mewarisi dari protokol ini akan ditemukan dan dibuat, dengan satu pengecualian - itu sendiri, yaitu, secara rekursif. Itu tidak akan membuat sendiri selama inisialisasi, meskipun dapat dengan aman melakukan ini ketika diimplementasikan melalui properti.

Tag, pada gilirannya, adalah tipe terpisah apa pun yang harus ditentukan selama penggunaan dan selama pendaftaran. Artinya, tag adalah kriteria tambahan jika tidak ada tipe dasar yang cukup.

Anda dapat membaca lebih lanjut tentang ini: Pengubah

Kehadiran pengubah, terutama `banyak`, membuat perpustakaan lebih baik daripada yang lain. Misalnya, Anda dapat menerapkan pola Observer pada tingkat yang sama sekali berbeda. Karena 4 huruf ini, dalam proyek itu mungkin untuk menghapus 30-50 baris kode dari setiap Pengamat dalam proyek dan memecahkan masalah dengan pertanyaan - di mana dan kapan benda harus ditambahkan ke Observable. Bisnis yang jelas bukan satu-satunya aplikasi, tetapi signifikan.

Baiklah, kami akan menyelesaikan presentasi fitur dengan memperkenalkan logger di YourPresenter:

 container.register(YourPresenter.init) .injection { $0.logger = $1 } 

Di sini, misalnya, ini ditulis sedikit berbeda dari sebelumnya - ini dilakukan untuk contoh sintaks yang berbeda.

Harap dicatat bahwa properti logger adalah opsional:

 internal var logger: Logger? 

Dan ini tidak muncul dalam sintaks perpustakaan. Berbeda dengan versi pertama, sekarang semua operasi untuk tipe yang biasa, opsional dan terpaksa opsional terlihat sama. Selain itu, logika di dalamnya berbeda - jika jenisnya opsional, dan tidak terdaftar dalam wadah, maka program tidak akan macet, tetapi akan melanjutkan eksekusi.

Ringkasan


Hasilnya mirip dengan yang terakhir kali, hanya sintaks telah menjadi lebih pendek dan lebih fungsional.

Apa yang ditinjau:



Apa lagi yang bisa dilakukan perpustakaan:



Paket


Pertama-tama, direncanakan untuk beralih ke memeriksa grafik pada tahap kompilasi - yaitu, integrasi yang lebih dekat dengan kompiler. Ada implementasi awal menggunakan SourceKitten, tetapi implementasi seperti itu memiliki kesulitan serius dengan inferensi tipe, sehingga direncanakan untuk beralih ke ast-dump - di swift5 menjadi bekerja pada proyek-proyek besar. Di sini saya ingin mengucapkan terima kasih kepada Nekitosss atas kontribusi besar dalam arah ini.

Kedua, saya ingin berintegrasi dengan layanan visualisasi. Ini akan menjadi proyek yang sedikit berbeda, tetapi terkait erat dengan perpustakaan. Apa gunanya Sekarang perpustakaan menyimpan seluruh grafik koneksi, yaitu, secara teori segala sesuatu yang terdaftar di perpustakaan dapat ditampilkan sebagai diagram kelas / komponen UML. Dan akan menyenangkan untuk kadang-kadang melihat diagram ini.

Fungsi ini direncanakan dalam dua bagian - bagian pertama akan memungkinkan Anda menambahkan API untuk mendapatkan semua informasi, dan yang kedua sudah terintegrasi dengan berbagai layanan.

Opsi paling sederhana adalah menampilkan grafik tautan dalam bentuk teks, tetapi saya belum melihat opsi yang dapat dibaca - jika demikian, sarankan opsi di komentar.

WatchOS - Saya sendiri tidak menulis proyek untuk mereka. Untuk hidupnya dia hanya menulis sekali, lalu kecil. Tetapi saya ingin membuat integrasi yang ketat, seperti dengan Storyboard.

Itu semua terima kasih atas perhatiannya. Saya sangat mengharapkan komentar dan jawaban survei.

Tentang diri saya
Ivlev Alexander Evgenievich - pemimpin senior / tim di tim iOS. Saya telah bekerja di perdagangan selama 7 tahun, di bawah iOS 4,5 tahun - sebelum itu saya adalah seorang pengembang C ++. Tetapi total pengalaman pemrograman lebih dari 15 tahun - di sekolah saya berkenalan dengan dunia yang menakjubkan ini dan begitu terbawa olehnya sehingga ada suatu periode ketika saya bertukar permainan , makanan, toilet, mimpi untuk menulis kode. Menurut salah satu artikel saya, Anda dapat menebak bahwa saya adalah mantan Olimpiade - karenanya, tidak sulit bagi saya untuk menulis karya yang kompeten dengan grafik. Kekhususan - sistem pengukur informasi, dan pada suatu waktu saya terobsesi dengan multithreading dan paralelisme - ya, saya menulis kode di mana saya membuat asumsi dan bug pada topik yang serupa, tetapi saya memahami area masalah dan sangat memahami di mana Anda dapat mengabaikan mutex, dan di mana tidak layak.

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


All Articles