Pengalaman saya tentang kesalahan
Daftar kesalahan
- MCManager kelas Mahakuasa
- Menciptakan navigasi kami di antara layar
- Tidak pernah ada banyak warisan
- Arsitektur produksi kami sendiri atau terus membuat sepeda
- MVVM dengan soul MVP
- Upaya kedua dengan navigasi atau Router dan kelengkungan navigasi
- Manajer yang gigih
Banyak orang, termasuk saya, menulis bagaimana melakukan hal yang benar dalam situasi tertentu, bagaimana menulis kode dengan benar, bagaimana menerapkan solusi arsitektur, dll. Tetapi saya ingin berbagi pengalaman saya tentang bagaimana hal itu dilakukan secara tidak benar dan kesimpulan yang saya dapat dibuat berdasarkan kesalahannya. Kemungkinan besar ini akan menjadi kesalahan umum semua orang yang mengikuti jalur pengembang, atau mungkin sesuatu akan baru. Saya hanya ingin berbagi pengalaman dan membaca komentar orang lain.
MCManager kelas Mahakuasa
Setelah tahun pertama bekerja di IT, dan lebih khusus lagi pengembangan iOS, saya memutuskan bahwa saya sudah menjadi seorang arsitek yang cukup dan cukup siap untuk dibuat. Bahkan kemudian, saya secara intuitif memahami bahwa perlu memisahkan logika bisnis dari lapisan presentasi. Tetapi kualitas ide saya tentang bagaimana melakukan ini jauh dari kenyataan.
Saya pindah ke tempat kerja baru, di mana saya ditugaskan untuk secara mandiri mengembangkan fitur baru untuk proyek yang ada. Itu analog dengan merekam video di Instagram, di mana perekaman dilakukan saat pengguna memegang jarinya pada tombol, dan kemudian beberapa fragmen video terhubung bersama. Awalnya, diputuskan untuk menjadikan fitur ini sebagai proyek terpisah, atau lebih tepatnya dalam bentuk sampel. Ini, seperti yang saya pahami, memulai sumber masalah arsitektur saya, yang berlangsung lebih dari setahun.
Di masa depan, sampel ini tumbuh menjadi aplikasi lengkap untuk merekam dan mengedit video. Lucu bahwa awalnya sampel memiliki nama, dari singkatan yang dikumpulkan awalan MC. Meskipun proyek itu segera diganti namanya, tetapi awalan seperti yang disyaratkan oleh konvensi nama di Objective-C, MC tetap. Jadi kelas MCManager yang maha kuasa lahir.

Karena ini adalah sampel dan pada awalnya fungsinya sederhana, saya memutuskan bahwa satu kelas manajer sudah cukup. Fungsionalitas, seperti yang saya sebutkan sebelumnya, termasuk merekam fragmen video dengan opsi mulai / hentikan dan selanjutnya menggabungkan fragmen-fragmen ini ke dalam keseluruhan video. Dan pada saat itu saya dapat menyebutkan kesalahan pertama saya - nama kelas MCManager. Manajer MC, Karl! Apa yang harus dikatakan nama kelas kepada pengembang lain tentang tujuan, kemampuannya, dan cara menggunakannya? Benar, sama sekali tidak ada! Dan ini ada dalam lampiran, nama yang bahkan tidak mengandung huruf M dan, ibunya, C. Meskipun, ini bukan kesalahan utama saya, karena kelas dengan nama partisan melakukan segalanya, segala sesuatu dari kata itu benar-benar segalanya, yang merupakan kesalahan utama.
Rekaman video adalah satu layanan kecil, mengelola penyimpanan file video dalam sistem file adalah layanan kedua dan tambahan untuk menggabungkan beberapa video menjadi satu. Pekerjaan tiga layanan independen ini, diputuskan untuk bergabung dalam satu manajer. Idenya adalah mulia, menggunakan pola fasad, untuk membuat antarmuka sederhana untuk logika bisnis dan menyembunyikan semua detail yang tidak perlu pada interaksi berbagai komponen. Pada tahap awal, bahkan nama kelas fasad seperti itu tidak menimbulkan kecurigaan, terutama dalam sampel.
Tetapi pelanggan menyukai demo dan segera sampel berubah menjadi aplikasi yang lengkap. Anda dapat membenarkan bahwa tidak ada cukup waktu untuk refactoring, bahwa pelanggan tidak ingin mengulang kode kerja, tetapi terus terang, pada saat itu saya sendiri berpikir bahwa saya telah meletakkan arsitektur yang sangat baik. Sebenarnya, ide untuk memisahkan logika bisnis dan presentasi adalah sukses. Arsitekturnya adalah satu kelas MCManager singleton, yang merupakan fasad untuk beberapa lusin layanan dan manajer lainnya. Ya, itu juga singleton, yang tersedia dari semua penjuru aplikasi.
Orang sudah bisa memahami skala seluruh bencana. Kelas dengan beberapa ribu baris kode yang sulit dibaca dan sulit dipertahankan. Saya sudah bungkam tentang kemungkinan menyoroti fitur individual untuk mentransfernya ke aplikasi lain, yang sangat umum dalam pengembangan ponsel.
Kesimpulan yang saya buat untuk diri saya setelah beberapa saat bukanlah untuk membuat kelas universal dengan nama yang tidak jelas. Saya menyadari bahwa logika perlu dipecah-pecah dan tidak membuat antarmuka universal untuk semuanya. Bahkan, ini adalah contoh dari apa yang akan terjadi jika Anda tidak mematuhi salah satu prinsip SOLID, Prinsip Segregasi Antarmuka.
Menciptakan navigasi kami di antara layar
Pemisahan logika dan antarmuka bukan satu-satunya masalah yang membuat saya khawatir tentang proyek di atas. Saya tidak akan mengatakan bahwa pada saat itu saya akan memisahkan kode layar dan kode navigasi, tetapi ternyata saya datang dengan sepeda saya untuk navigasi.

Sampel hanya memiliki tiga layar: menu dengan tabel video yang direkam, layar perekaman dan layar pasca pemrosesan. Agar tidak peduli bahwa tumpukan navigasi mengandung duplikat ViewControllers, saya memutuskan untuk tidak menggunakan UINavigationController. Saya menambahkan RootViewcontroller, pembaca yang penuh perhatian telah menebak bahwa itu adalah MCRootViewController, yang ditetapkan sebagai yang utama dalam pengaturan proyek. Pada saat yang sama, pengontrol root bukan salah satu layar aplikasi, ia hanya menyajikan UIViewController yang diinginkan. Seolah-olah ini tidak cukup, jadi root controller juga merupakan delegasi dari semua pengendali yang diwakili. Akibatnya, pada setiap saat dalam waktu hanya ada dua vc dalam hierarki, dan semua navigasi diimplementasikan menggunakan pola delegasi.
Bagaimana kelihatannya: setiap layar memiliki protokol delegasi sendiri, di mana metode navigasi ditunjukkan, dan pengendali root menerapkan metode ini dan mengubah layar. RootViewController dissmisil current controller, membuat yang baru dan mempresentasikannya, sementara itu dimungkinkan untuk mentransfer informasi dari satu layar ke yang lain. Untungnya, logika bisnis berada di kelas tunggal paling keren, sehingga tidak ada layar yang menyimpan apa pun dan dapat dengan mudah dihancurkan. Sekali lagi, niat yang baik terwujud, meskipun realisasi tertatih-tatih di kedua kaki, dan kadang-kadang tersandung.
Seperti yang Anda duga, jika Anda harus kembali dari layar perekaman video ke menu utama, metode itu disebut:
- (void)cancel;
atau sesuatu seperti itu, dan pengontrol root sudah melakukan semua pekerjaan kotor.
Akibatnya, MCRootViewController, menjadi analog MCManager, tetapi dalam navigasi antar layar, seiring dengan pertumbuhan aplikasi dan penambahan fungsi baru, layar baru ditambahkan.
Pabrik sepeda bekerja dengan sangat baik, dan saya terus mengabaikan artikel tentang arsitektur aplikasi seluler. Tapi saya tidak pernah menyerah untuk memisahkan navigasi dari layar.
Keuntungannya adalah bahwa layarnya independen dan dapat digunakan kembali, tetapi ini tidak akurat. Namun kerugiannya termasuk kesulitan dalam mempertahankan kelas-kelas tersebut. Masalah dengan kurangnya tumpukan layar ketika Anda harus kembali dengan menggulir layar yang dipilih sebelumnya. Logika transisi yang rumit antara layar, pengontrol root mempengaruhi bagian dari logika bisnis untuk menampilkan layar baru dengan benar.
Secara umum, Anda tidak boleh menerapkan semua navigasi dalam aplikasi dengan cara ini, karena MCRootViewController saya melanggar prinsip Prinsip Terbuka-Tertutup. Hampir mustahil untuk berkembang, dan semua perubahan harus dilakukan secara konstan ke kelas itu sendiri.
Saya mulai membaca lebih lanjut tentang navigasi antar layar dalam aplikasi seluler, berkenalan dengan pendekatan seperti Router dan Koordinator. Saya akan menulis tentang Router sedikit kemudian, karena ada sesuatu untuk dibagikan.
Tidak pernah ada banyak warisan
Saya juga ingin membagikan tidak hanya mutiara saya, tetapi juga pendekatan dan solusi lucu orang lain yang harus saya tangani. Di tempat yang sama di mana saya menciptakan karya besar saya, mereka mempercayakan saya dengan tugas sederhana. Tugasnya adalah menambahkan layar dari proyek lain ke proyek saya. Seperti yang kami tentukan dengan PM, setelah analisis yang dangkal dan sedikit pemikiran, ini seharusnya memakan waktu dua atau tiga jam dan tidak lebih, karena apa yang salah dengan ini, Anda hanya perlu menambahkan kelas layar yang sudah jadi ke aplikasi Anda. Memang, semuanya sudah dilakukan untuk kita, kita perlu melakukan ctrl + c dan ctrl + v. Ini hanya sedikit nuansa, pengembang yang menulis aplikasi ini sangat menyukai warisan.

Saya segera menemukan ViewController yang saya butuhkan, saya beruntung tidak ada pemisahan logika dan presentasi. Itu adalah pendekatan lama yang baik, ketika controller berisi semua kode yang diperlukan. Saya menyalinnya ke proyek saya dan mulai mencari cara untuk membuatnya bekerja. Dan hal pertama yang saya temukan adalah bahwa pengontrol yang saya butuhkan mewarisi dari pengontrol lain. Suatu hal yang umum, acara yang sangat diharapkan. Karena saya tidak punya banyak waktu, saya hanya menemukan kelas yang saya butuhkan dan menyeretnya ke proyek saya. Nah sekarang itu seharusnya bekerja, pikirku, dan aku tidak pernah salah!
Tidak hanya kelas yang saya butuhkan memiliki banyak variabel kelas kustom yang juga perlu disalin ke proyek saya, sehingga masing-masing dari mereka mewarisi sesuatu. Pada gilirannya, kelas dasar adalah bidang yang diwariskan atau berisi dengan tipe khusus yang, seperti yang mungkin ditebak banyak orang, mewarisi sesuatu, dan sayangnya, ini bukan NSObject, UIViewController atau UIView. Dengan demikian, sepertiga dari proyek yang tidak perlu bermigrasi ke saya dalam proyek.
Karena tidak banyak waktu yang diharapkan untuk menyelesaikan tugas ini, saya tidak melihat jalan keluar lain dari cara menambahkan kelas yang diperlukan yang dibutuhkan xCode hanya untuk meluncurkan proyek saya tanpa rasa sakit. Akibatnya, dua atau tiga jam berjalan sedikit, karena pada akhirnya saya harus mempelajari seluruh jaringan hierarki warisan, seperti pematung sejati, memotong kelebihannya.
Sebagai hasilnya, saya sampai pada kesimpulan bahwa semua hal baik harus secukupnya, bahkan hal yang "luar biasa" sebagai warisan. Kemudian saya mulai memahami kerugian warisan. Saya menyimpulkan sendiri, jika saya ingin membuat modul yang dapat digunakan kembali, saya harus membuatnya lebih mandiri.
Arsitektur produksi kami sendiri atau terus membuat sepeda
Pindah ke tempat kerja baru dan memulai proyek baru, saya memperhitungkan semua pengalaman yang tersedia dalam desain arsitektur dan terus menciptakan. Secara alami, saya terus mengabaikan arsitektur yang sudah ditemukan, tetapi pada saat yang sama saya tetap berpegang pada prinsip "memecah belah dan menaklukkan".
Tidak lama sebelum kedatangan Swift, jadi saya menyelidiki kemungkinan Objective-c. Saya memutuskan untuk melakukan injeksi Ketergantungan menggunakan fitur bahasa. Saya terinspirasi oleh alat ekspansi Lib, saya bahkan tidak dapat mengingat namanya.
Intinya adalah: di kelas dasar BaseViewController, saya menambahkan bidang kelas BaseViewModel. Dengan demikian, untuk setiap layar saya membuat pengontrol saya sendiri, yang mewarisi yang dasar, dan menambahkan protokol agar pengontrol berinteraksi dengan viewModel. Kemudian muncullah keajaiban. Saya mendefinisikan kembali properti viewModel dan menambahkan dukungan untuk protokol yang diinginkan. Pada gilirannya, saya membuat kelas ViewModel baru untuk layar tertentu yang menerapkan protokol ini. Akibatnya, dalam BaseViewController dalam metode viewDidLoad, saya memeriksa jenis protokol model, memeriksa daftar semua keturunan BaseViewModel, menemukan kelas yang saya butuhkan dan membuat viewModel dari tipe yang saya butuhkan.
Contoh Dasar ViewController #import <UIKit/UIKit.h> // MVC model #import "BaseMVCModel.h" @class BaseViewController; @protocol BaseViewControllerDelegate <NSObject> @required - (void)backFromNextViewController:(BaseViewController *)aNextViewController withOptions:(NSDictionary *)anOptionsDictionary; @end @interface BaseViewController : UIViewController <BaseViewControllerDelegate> @property (nonatomic, weak) BaseMVCModel *model; @property (nonatomic, assign) id<BaseViewControllerDelegate> prevViewController; - (void)backWithOptions:(NSDictionary *)anOptionsDictionary; + (void)setupUIStyle; @end import "BaseViewController.h" // Helpers #import "RuntimeHelper.h" @interface BaseViewController () @end @implementation BaseViewController + (void)setupUIStyle { } #pragma mark - #pragma mark Life cycle - (void)viewDidLoad { [super viewDidLoad]; self.model = [BaseMVCModel getModel:FindPropertyProtocol(@"model", [self class])]; } #pragma mark - #pragma mark Navigation - (void)backWithOptions:(NSDictionary *)anOptionsDictionary { if (self.prevViewController) { [self.prevViewController performSelector:@selector(backFromNextViewController:withOptions:) withObject:self withObject:anOptionsDictionary]; } } #pragma mark - #pragma mark Seque - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.destinationViewController isKindOfClass:[BaseViewController class]] { ((BaseViewController *)segue.destinationViewController).prevViewController = self; } } #pragma mark - #pragma mark BaseViewControllerDelegate - (void)backFromNextViewController:(BaseViewController *)aNextViewController withOptions:(NSDictionary *)anOptionsDictionary { [self doesNotRecognizeSelector:_cmd]; } @end
Contoh ViewModel Dasar #import <Foundation/Foundation.h> @interface BaseMVCModel : NSObject @property (nonatomic, assign) id delegate; + (id)getModel:(NSString *)someProtocol; @end #import "BaseMVCModel.h" // IoC #import "IoCContainer.h" @implementation BaseMVCModel + (id)getModel:(NSString *)someProtocol { return [[IoCContainer sharedIoCContainer] getModel:NSProtocolFromString(someProtocol)]; } @end
Kelas pembantu #import <Foundation/Foundation.h> @interface IoCContainer : NSObject + (instancetype)sharedIoCContainer; - (id)getModel:(Protocol *)someProtocol; @end #import "IoCContainer.h" // Helpers #import "RuntimeHelper.h" // Models #import "BaseMVCModel.h" @interface IoCContainer () @property (nonatomic, strong) NSMutableSet *models; @end @implementation IoCContainer #pragma mark - #pragma mark Singleton + (instancetype)sharedIoCContainer { static IoCContainer *_sharedIoCContainer = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedIoCContainer = [IoCContainer new]; }); return _sharedIoCContainer; } - (id)getModel:(Protocol *)someProtocol { if (!someProtocol) { return [BaseMVCModel new]; } NSArray *modelClasses = ClassGetSubclasses([BaseMVCModel class]); __block Class currentClass = NULL; [modelClasses enumerateObjectsUsingBlock:^(Class class, NSUInteger idx, BOOL *stop) { if ([class conformsToProtocol:someProtocol]) { currentClass = class; } }]; if (currentClass == nil) { return [BaseMVCModel new]; } __block BaseMVCModel *currentModel = nil; [self.models enumerateObjectsUsingBlock:^(id model, BOOL *stop) { if ([model isKindOfClass:currentClass]) { currentModel = model; } }]; if (!currentModel) { currentModel = [currentClass new]; [self.models addObject:currentModel]; } return currentModel; } - (NSMutableSet *)models { if (!_models) { _models = [NSMutableSet set]; } return _models; } @end #import <Foundation/Foundation.h> NSString * FindPropertyProtocol(NSString *propertyName, Class class); NSArray * ClassGetSubclasses(Class parentClass); #import "RuntimeHelper.h" #import <objc/runtime.h> #pragma mark - #pragma mark Functions NSString * FindPropertyProtocol(NSString *aPropertyName, Class class) { unsigned int propertyCount; objc_property_t *properties = class_copyPropertyList(class, &propertyCount); for (unsigned int i = 0; i < propertyCount; i++) { objc_property_t property = properties[i]; const char *propertyName = property_getName(property); if ([@(propertyName) isEqualToString:aPropertyName]) { const char *attrs = property_getAttributes(property); NSString* propertyAttributes = @(attrs); NSScanner *scanner = [NSScanner scannerWithString: propertyAttributes]; [scanner scanUpToString:@"<" intoString:NULL]; [scanner scanString:@"<" intoString:NULL]; NSString* protocolName = nil; [scanner scanUpToString:@">" intoString: &protocolName]; return protocolName; } } return nil; } NSArray * ClassGetSubclasses(Class parentClass) { int numClasses = objc_getClassList(NULL, 0); Class *classes = NULL; classes = (Class *)malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); NSMutableArray *result = [NSMutableArray array]; for (NSInteger i = 0; i < numClasses; i++) { Class superClass = classes[i]; do { superClass = class_getSuperclass(superClass); } while(superClass && superClass != parentClass); if (superClass == nil) { continue; } [result addObject:classes[i]]; } free(classes); return result; }
Contoh layar masuk #import "BaseViewController.h" @protocol LoginProtocol <NSObject> @required - (void)login:(NSString *)aLoginString password:(NSString *)aPasswordString completionBlock:(DefaultCompletionBlock)aCompletionBlock; @end @interface LoginVC : BaseViewController @end #import "LoginVC.h" #import "UIViewController+Alert.h" #import "UIViewController+HUD.h" @interface LoginVC () @property id<LoginProtocol> model; @property (weak, nonatomic) IBOutlet UITextField *emailTF; @property (weak, nonatomic) IBOutlet UITextField *passTF; @end @implementation LoginVC @synthesize model = _model; #pragma mark - #pragma mark IBActions - (IBAction)loginAction:(id)sender { [self login]; } #pragma mark - #pragma mark UITextFieldDelegate - (BOOL)textFieldShouldReturn:(UITextField *)textField { if (textField == self.emailTF) { [self.passTF becomeFirstResponder]; } else { [self login]; } return YES; } #pragma mark - #pragma mark Login - (void)login { NSString *email = self.emailTF.text; NSString *pass = self.passTF.text; if (email.length == 0 || pass.length == 0) { [self showAlertOkWithMessage:@"Please, input info!"]; return; } __weak __typeof(self)weakSelf = self; [self showHUD]; [self.model login:self.emailTF.text password:self.passTF.text completionBlock:^(BOOL isDone, NSError *anError) { [weakSelf hideHUD]; if (isDone) { [weakSelf backWithOptions:nil]; } }]; } @end
Sedemikian mudahnya, saya melakukan inisialisasi malas pada viewModel dan menghubungkannya dengan model melalui protokol. Dengan semua ini, pada saat itu saya masih tidak tahu apa-apa tentang arsitektur MVP, meskipun sesuatu yang serupa membayangi saya.
Navigasi antara layar diserahkan kepada kebijaksanaan "viewModel", karena saya menambahkan tautan yang lemah ke controller.
Mengingat implementasi ini sekarang, saya tidak dapat mengatakan dengan pasti bahwa semuanya buruk. Gagasan memisahkan lapisan berhasil, momen membuat dan menetapkan model ke controller disederhanakan.
Tetapi bagi saya sendiri, saya memutuskan untuk mempelajari lebih banyak pendekatan dan arsitektur yang sudah jadi, karena selama pengembangan aplikasi dengan arsitektur saya sendiri, saya harus berurusan dengan banyak nuansa. Misalnya, penggunaan kembali layar dan model, pewarisan, transisi kompleks antar layar. Pada saat itu, tampak bagi saya bahwa viewModel adalah bagian dari logika bisnis, walaupun sekarang saya mengerti bahwa itu masih merupakan lapisan presentasi. Saya mendapat pengalaman hebat selama percobaan ini.
MVVM dengan soul MVP
Setelah mendapatkan pengalaman, saya memutuskan untuk memilih arsitektur khusus untuk diri saya dan mengikutinya, daripada menciptakan sepeda. Saya mulai membaca lebih banyak tentang arsitektur, untuk belajar secara mendetail populer pada waktu itu dan menetap di MVVM. Terus terang, saya tidak segera mengerti esensinya, tetapi saya memilihnya karena saya suka namanya.
Saya tidak segera memahami esensi arsitektur dan hubungan antara ViewModel dan View (ViewController), tetapi mulai melakukan seperti yang saya mengerti. Mata takut, dan tangan mengetik kode dengan panik.
Dalam pembelaan saya, saya akan menambahkan bahwa pada waktu itu waktu dan waktu untuk berpikir dan menganalisis penciptaan yang saya buat sangat ketat. Oleh karena itu, alih-alih binder, saya membuat tautan langsung di ViewModel ke View yang sesuai. Dan sudah di ViewModel sendiri, saya melakukan pengaturan presentasi.
Tentang MVP, saya memiliki ide yang sama seperti tentang arsitektur lain, jadi saya sangat percaya bahwa itu adalah MVVM, di mana ViewModel ternyata menjadi presenter paling nyata.
Contoh arsitektur "MVVM" saya dan ya, saya menyukai gagasan itu dengan RootViewController, yang bertanggung jawab atas navigasi tingkat tertinggi dalam aplikasi. Tentang router ditulis di bawah ini. import UIKit class RootViewController: UIViewController { var viewModel: RootViewModel? override func viewDidLoad() { super.viewDidLoad() let router = (UIApplication.shared.delegate as? AppDelegate)!.router viewModel = RootViewModel(with: self, router: router) viewModel?.setup() } } import UIKit protocol ViewModelProtocol: class { func setup() func backAction() } class RootViewModel: NSObject, ViewModelProtocol { unowned var router : RootRouter unowned var view: RootViewController init(with view: RootViewController, router: RootRouter) { self.view = view self.router = router }
Ini tidak secara khusus mempengaruhi kualitas proyek, karena urutan dan pendekatan tunggal dihormati. Tapi pengalaman itu sangat berharga. Setelah sepeda yang saya buat, saya akhirnya mulai melakukan sesuai dengan arsitektur yang diterima secara umum. Kecuali presenter dipanggil presenter, yang bisa membingungkan pengembang pihak ketiga.
Saya memutuskan bahwa di masa depan ada baiknya melakukan proyek uji kecil, untuk menggali esensi dari pendekatan tertentu dalam desain secara lebih rinci. Jadi untuk berbicara, rasakan terlebih dahulu dalam latihan, dan kemudian masuk ke pertempuran. Demikian kesimpulan yang saya buat untuk diri saya sendiri.
Upaya kedua dengan navigasi atau Router dan kelengkungan navigasi
Pada proyek yang sama, di mana saya dengan gagah dan naif mengimplementasikan MVVM, saya memutuskan untuk mencoba pendekatan baru dalam navigasi antar layar. Seperti yang saya sebutkan sebelumnya, saya masih menganut gagasan memisahkan layar dan logika transisi di antara mereka.
Membaca tentang MVVM, saya tertarik dengan pola seperti Router. Setelah meninjau kembali deskripsinya, saya mulai mengimplementasikan solusi dalam proyek saya.
Contoh router import UIKit protocol Router: class { func route(to routeID: String, from view: UIViewController, parameters: Any?) func back(from view: UIViewController, parameters: Any?) } extension Router { func back(from view: UIViewController, parameters: Any?) { let navigationController: UINavigationController = checkNavigationController(for: view) navigationController.popViewController(animated: false) } } enum RootRoutes: String { case launch = "Launch" case loginregistartion = "LoginRegistartionRout" case mainmenu = "MainMenu" } class RootRouter: Router { var loginRegistartionRouter: LoginRegistartionRouter? var mainMenuRouter: MainMenuRouter?
Kurangnya pengalaman dalam menerapkan pola semacam itu telah membuatnya terasa. Semuanya tampak rapi dan jelas, router menciptakan kelas UIViewController baru, menciptakan ViewModel untuk itu dan mengeksekusi logika untuk beralih ke layar ini. Namun tetap saja, banyak kekurangan membuat diri mereka terasa.
Kesulitan mulai timbul ketika itu perlu untuk membuka aplikasi dengan layar tertentu setelah pemberitahuan push. Akibatnya, di beberapa tempat kami mendapat logika yang membingungkan untuk memilih layar yang tepat dan kesulitan lebih lanjut dalam mendukung pendekatan semacam itu.
Saya tidak meninggalkan ide untuk mengimplementasikan Router, tetapi melanjutkan ke arah ini, mendapatkan lebih banyak pengalaman. Jangan menyerah sesuatu setelah upaya pertama gagal.
Manajer yang gigih
Kelas manajer lain yang menarik dalam praktik saya. Tapi yang ini relatif muda. Semua sama, proses pengembangan terdiri dari coba-coba, dan karena kita semua, baik, atau kebanyakan dari kita, terus-menerus dalam proses pengembangan, kesalahan selalu muncul.
Inti dari masalahnya adalah ini, aplikasi memiliki layanan yang harus digantung terus-menerus dan pada saat yang sama harus tersedia di banyak tempat.
Contoh: menentukan status Bluetooth. Dalam aplikasi saya, dalam beberapa layanan saya perlu memahami apakah bluetooth dihidupkan atau dimatikan dan berlangganan pembaruan status. Karena ada beberapa tempat seperti itu: beberapa layar, beberapa manajer logika bisnis tambahan, dll., Masing-masing dari mereka perlu berlangganan delegasi CBPeripheralManager (atau CBCentralManager).
Solusinya tampaknya jelas, kami membuat kelas terpisah yang memonitor status bluetooth dan memberi tahu semua orang yang membutuhkannya melalui pola Observer. Tetapi kemudian muncul pertanyaan, siapa yang akan secara permanen menyimpan layanan ini? Hal pertama yang terlintas dalam pikiran saat ini adalah menjadikannya singleton! Segalanya tampak baik-baik saja!
Tapi inilah saatnya bahwa lebih dari satu layanan tersebut telah terakumulasi dalam aplikasi saya. Saya juga tidak ingin membuat 100500 singletones dalam proyek ini.
Dan kemudian cahaya lain muncul di atas kepala kecilku yang sudah terang. Buat satu singleton yang akan menyimpan semua layanan tersebut dan menyediakan akses ke mereka di seluruh aplikasi. Maka lahirlah "manajer permanen". Dengan nama itu, saya tidak berpikir lama dan menyebutnya, karena semua orang sudah bisa menebak, PersistentManager.
Seperti yang Anda lihat, saya juga memiliki pendekatan yang sangat orisinal untuk penamaan kelas. Saya pikir saya perlu menambahkan sedikit tentang nama kelas dalam rencana pengembangan saya.
Masalah utama dalam implementasi ini adalah singleton, yang tersedia di mana saja dalam proyek ini. Dan ini mengarah pada fakta bahwa manajer yang menggunakan salah satu layanan permanen mengaksesnya di dalam metode mereka, yang tidak jelas. Untuk pertama kalinya, saya menemukan ini ketika saya membuat fitur kompleks besar dalam proyek demo terpisah dan mentransfer bagian dari logika bisnis dari proyek utama. Kemudian saya mulai menerima pesan dengan kesalahan tentang layanan yang hilang.
Kesimpulan yang saya buat setelah ini adalah bahwa Anda perlu mendesain kelas Anda sedemikian rupa sehingga tidak ada dependensi tersembunyi. Layanan yang diperlukan harus diberikan sebagai parameter saat menginisialisasi kelas, tetapi tidak menggunakan singleton, yang dapat diakses dari mana saja. Dan yang lebih indah, perlu dilakukan dengan menggunakan protokol.
Ini ternyata merupakan konfirmasi lain dari kurangnya pola tunggal.
Ringkasan
Dan itu, saya tidak berdiri diam, tetapi bergerak maju, menguasai pendekatan baru dalam pemrograman. Hal utama adalah bergerak, mencari, dan bereksperimen. Kesalahan akan selalu terjadi, tidak ada jalan keluar dari ini. Tetapi hanya karena pengakuan kesalahan seseorang, seseorang dapat berkembang secara kualitatif.

Dalam kebanyakan kasus, masalahnya adalah kelas super yang melakukan banyak hal, atau ketergantungan yang salah antar kelas. Yang menunjukkan bahwa perlu untuk menguraikan logika lebih kompeten.