تجربتي في الاخطاء
قائمة الأخطاء
- الطبقة الكلية MCManager
- اختراع الملاحة لدينا بين الشاشات
- لا يوجد الكثير من الميراث
- هندسة الإنتاج الخاصة بنا أو الاستمرار في إنشاء الدراجات
- MVVM مع الروح MVP
- المحاولة الثانية مع الملاحة أو جهاز التوجيه وانحناء الملاحة
- المدير المستمر
كثير من الناس ، بمن فيهم أنا ، يكتبون كيفية القيام بالشيء الصحيح في موقف معين ، وكيفية كتابة التعليمات البرمجية بشكل صحيح ، وكيفية تطبيق الحلول المعمارية ، وما إلى ذلك. لكن أود أن أشارك تجربتي في كيفية القيام بذلك بشكل غير صحيح والاستنتاجات التي أستخدمها. مصنوعة على أساس أخطائه. على الأرجح ستكون هذه أخطاء شائعة لكل من يتبع مسار المطور ، أو ربما يكون هناك شيء جديد. أريد فقط مشاركة تجربتي وقراءة تعليقات اللاعبين الآخرين.
الطبقة الكلية MCManager
بعد السنة الأولى من العمل في مجال تكنولوجيا المعلومات ، وبشكل أكثر تحديداً تطوير نظام التشغيل iOS ، قررت أنني بالفعل مهندس معماري بما فيه الكفاية وعلى استعداد تام للتصميم. حتى ذلك الحين ، فهمت حدسي أنه من الضروري فصل منطق العمل عن طبقة العرض التقديمي. ولكن نوعية فكرتي عن كيفية القيام بذلك كانت بعيدة عن الواقع.
انتقلت إلى مكان عمل جديد ، حيث تم تكليفي بتطوير ميزة جديدة بشكل مستقل لمشروع قائم. كان ذلك بمثابة تناظر لتسجيل مقاطع الفيديو على Instagram ، حيث يتم إجراء التسجيل بينما يمسك المستخدم إصبعه على الزر ، ثم يتم توصيل العديد من أجزاء الفيديو معًا. في البداية ، تقرر جعل هذه الميزة كمشروع منفصل ، أو في شكل عينة. هذا ، كما أفهمها ، يبدأ مصدر مشاكلي المعمارية ، التي استمرت أكثر من عام.
في المستقبل ، نمت هذه العينة لتصبح تطبيقًا كاملاً لتسجيل مقاطع الفيديو وتحريرها. من المضحك أنه في البداية كان للعينة اسم ، تم اختصار بادئة MC منه. على الرغم من إعادة تسمية المشروع قريبًا ، إلا أن البادئة كما هو مطلوب بموجب اصطلاح الاسم في الهدف- C ، ظلت MC. لذلك ولدت الطبقة MCManager سبحانه وتعالى.

نظرًا لأنه كان عينة وفي البداية كانت الوظيفة بسيطة ، فقد قررت أن فئة مدير واحدة ستكون كافية. لقد شملت الوظيفة ، كما ذكرت سابقًا ، تسجيل جزء فيديو مع خيارات بدء / إيقاف ومزيد من دمج هذه الأجزاء في فيديو كامل. وفي تلك اللحظة بالذات ، يمكنني تسمية خطأي الأول - اسم فئة MCManager. MCManager ، كارل! ماذا يجب أن يقول اسم الفصل للمطورين الآخرين عن الغرض منه ، وقدراته ، وكيفية استخدامه؟ صحيح ، لا شيء على الإطلاق! وهذا في الملحق ، الذي لا يحتوي اسمه حتى على الأحرف M ووالدته C. على الرغم من أن هذا ليس خطأي الرئيسي ، لأن الفصل الذي يحمل الاسم الحزبي فعل كل شيء ، وكل شيء من الكلمة هو كل شيء على الإطلاق ، والذي كان الخطأ الرئيسي.
تسجيل الفيديو هو خدمة واحدة صغيرة ، وإدارة تخزين ملفات الفيديو في نظام الملفات هي الخدمة الثانية والإضافية لدمج العديد من مقاطع الفيديو في واحدة. عمل هذه الخدمات المستقلة الثلاثة ، تقرر الجمع في مدير واحد. كانت الفكرة نبيلة ، باستخدام نمط الواجهة ، لإنشاء واجهة بسيطة لمنطق الأعمال وإخفاء جميع التفاصيل غير الضرورية حول تفاعل المكونات المختلفة. في المراحل الأولية ، لم يثر اسم فئة الواجهة هذه أي شكوك ، خاصة في العينة.
لكن العميل أحب العرض التجريبي وسرعان ما تحولت العينة إلى تطبيق كامل. يمكنك تبرير أنه لم يكن هناك ما يكفي من الوقت لإعادة البناء ، وأن العميل لم يرغب في إعادة رمز العمل ، ولكن بصراحة ، في تلك اللحظة ، أعتقدت أنني قد وضعت بنية ممتازة. في الحقيقة ، كانت فكرة الفصل بين منطق الأعمال والعرض التقديمي ناجحة. كانت الهندسة المعمارية من فئة واحدة من MCManager المنفردة ، والتي كانت واجهة لبضع عشرات الخدمات وغيرها من المديرين. نعم ، كان أيضًا مفردة ، كانت متوفرة من جميع أركان التطبيق.
يمكن للمرء أن يفهم بالفعل حجم الكارثة بأكملها. فصل يحتوي على عدة آلاف من أسطر التعليمات البرمجية يصعب قراءتها ويصعب صيانتها. أنا صامت بالفعل بشأن إمكانية تسليط الضوء على الميزات الفردية من أجل نقلها إلى تطبيق آخر ، وهو أمر شائع للغاية في تطوير المحمول.
الاستنتاجات التي خلصت إليها بنفسي بعد فترة من الوقت لا تتمثل في إنشاء فصول عالمية بأسماء غامضة. أدركت أن المنطق يحتاج إلى تقسيم إلى أجزاء وعدم إنشاء واجهة عالمية لكل شيء. في الواقع ، كان هذا مثالًا لما سيحدث إذا لم تلتزم بأحد مبادئ SOLID ، مبدأ فصل الواجهة.
اختراع الملاحة لدينا بين الشاشات
فصل المنطق والواجهة ليست القضية الوحيدة التي أثارت قلقي بشأن المشروع أعلاه. لن أقول أنه في تلك اللحظة كنت سأقوم بفصل رمز الشاشة ورمز التنقل ، لكن اتضح أنني وصلت بدراجتي للملاحة.

كان للعينة ثلاث شاشات فقط: قائمة بها جدول فيديوهات مسجلة وشاشة تسجيل وشاشة ما بعد المعالجة. من أجل عدم الحرص على احتواء مكدس التنقل على ViewControllers مكررة ، قررت عدم استخدام UINavigationController. أضفت RootViewcontroller ، لقد خمنت القراء اليقظين بالفعل أنه كان MCRootViewController ، والذي تم تعيينه باعتباره الرئيسي في إعدادات المشروع. في الوقت نفسه ، لم تكن وحدة التحكم في الجذر واحدة من شاشات التطبيق ، لقد قدمت وحدة التحكم في UIViewController المطلوبة فقط. كما لو أن هذا لم يكن كافيًا ، لذلك كانت وحدة التحكم في الجذر أيضًا مفوضة لجميع وحدات التحكم الممثلة. نتيجة لذلك ، في كل لحظة من الزمن ، كان هناك فقط جهازي في التسلسل الهرمي ، وتم تنفيذ كل التنقل باستخدام نمط التفويض.
كيف بدا الأمر: كان لكل شاشة بروتوكول مفوض خاص بها ، حيث تمت الإشارة إلى طرق التنقل ، وقام جهاز التحكم في الجذر بتنفيذ هذه الطرق وتغيير الشاشات. يقوم RootViewController بإلغاء تحديد وحدة التحكم الحالية ، وإنشاء واحدة جديدة وعرضها ، بينما كان من الممكن نقل المعلومات من شاشة إلى أخرى. لحسن الحظ ، كان منطق الأعمال في أرقى درجات فردية ، لذلك لم تخزن أي من الشاشات أي شيء ويمكن تدميره دون ألم. مرة أخرى ، تم تحقيق نية حسنة ، على الرغم من أن الإدراك تعثر في كلتا الساقين ، وتعثر في بعض الأحيان.
كما قد تعتقد ، إذا كنت بحاجة إلى الانتقال من شاشة تسجيل الفيديو مرة أخرى إلى القائمة الرئيسية ، فقد تم استدعاء الطريقة:
- (void)cancel;
أو شيء من هذا القبيل ، وحدة تحكم الجذر تقوم بالفعل بكل العمل القذر.
في النهاية ، أصبح MCRootViewController ، نظيرًا لـ MCManager ، ولكن في التنقل بين الشاشات ، كما هو الحال مع نمو التطبيق وإضافة وظائف جديدة ، تمت إضافة شاشات جديدة.
لقد عمل مصنع الدراجات بلا هوادة ، وواصلتُ تجاهل مقالات عن أبنية تطبيقات الهاتف المحمول. لكنني لم أتخلى عن فكرة فصل التنقل عن الشاشات.
كانت الميزة أن الشاشات كانت مستقلة ويمكن إعادة استخدامها ، لكن هذا ليس دقيقًا. لكن العيوب تشمل صعوبة في الحفاظ على مثل هذه الطبقات. مشكلة عدم وجود مكدس من الشاشات عندما تحتاج إلى العودة من خلال التمرير عبر الشاشات المحددة مسبقًا. المنطق المعقدة للانتقال بين الشاشات ، وحدة تحكم الجذر تتأثر جزء من منطق الأعمال من أجل عرض شاشة جديدة بشكل صحيح.
بشكل عام ، يجب عدم تنفيذ كل التنقل في التطبيق بهذه الطريقة ، لأن MCRootViewController الخاص بي انتهك مبدأ المبدأ المفتوح. يكاد يكون من المستحيل التوسع ، ويجب إجراء جميع التغييرات باستمرار على الفصل نفسه.
بدأت في قراءة المزيد حول التنقل بين الشاشات في تطبيق الهاتف المحمول ، وتعرفت على أساليب مثل Router and Coordinator. سأكتب عن جهاز التوجيه لاحقًا ، نظرًا لوجود شيء للمشاركة.
لا يوجد الكثير من الميراث
أريد أيضًا مشاركة ليس فقط لآلئي ، ولكن أيضًا في مقاربات وحلول مضحكة لأشخاص آخرين اضطررت إلى التعامل معها ، وفي نفس المكان الذي صنعت فيه روائعي ، كلفوني بمهمة بسيطة. كانت المهمة لإضافة شاشة من مشروع آخر إلى مشروعي. كما قررنا مع PM ، بعد تحليل ضحل وفكر قليل ، كان يجب أن يستغرق هذا ساعتين أو ثلاث ساعات وليس أكثر ، لأن ما هو الخطأ في ذلك ، تحتاج فقط إلى إضافة فئة شاشة جاهزة إلى التطبيق الخاص بك. في الواقع ، لقد تم كل شيء بالفعل بالنسبة لنا ، نحن بحاجة إلى القيام به ctrl + c و ctrl + v. هنا مجرد فارق بسيط ، المطور الذي كتب هذا التطبيق أحب الميراث حقًا.

لقد وجدت بسرعة ViewController كنت بحاجة ، وكنت محظوظاً لأنه لم يكن هناك فصل بين المنطق والعرض التقديمي. لقد كان أسلوبًا قديمًا جيدًا ، عندما احتوى جهاز التحكم على كل الكود الضروري. لقد قمت بنسخه في مشروعي وبدأت في معرفة كيفية إنجاحه. وأول شيء اكتشفته هو أن وحدة التحكم التي أحتاجها ترث من وحدة تحكم أخرى. شيء شائع ، حدث متوقع تمامًا. نظرًا لعدم توفر الوقت الكافي ، وجدت الفئة التي احتاجها وأجرتها إلى مشروعي. حسنًا ، يجب أن تعمل الآن ، ولم أكن مخطئًا أبدًا!
لم يقتصر الأمر على الفصل الذي احتاجه ، حيث كان هناك العديد من متغيرات الفصول المخصصة التي تحتاج أيضًا إلى نسخها إلى مشروعي ، لذلك فقد ورث كل منهم شيئًا. في المقابل ، كانت الفئات الأساسية إما حقول موروثة أو تحتوي على أنواع مخصصة ، كما قد يكون كثيرون قد خمنوا ، ورثت شيئًا ما ، وهذا ، لسوء الحظ ، لم يكن NSObject أو UIViewController أو UIView. وهكذا ، هاجر لي ثلث المشروع غير الضروري في المشروع.
نظرًا لأنه لم يكن هناك الكثير من الوقت المتوقع لإكمال هذه المهمة ، لم أر أي طريقة أخرى لمعرفة كيفية إضافة الفئات الضرورية التي تتطلبها الكود ببساطة لإطلاق مشروعي دون ألم. ونتيجة لذلك ، استمرت ساعتان أو ثلاث ساعات قليلاً ، كما في النهاية ، كان عليّ أن أتعمق في الشبكة الكاملة للتسلسل الهرمي للميراث ، مثل النحات الحقيقي ، الذي يقطع الزائدة.
ونتيجة لذلك ، توصلت إلى استنتاج مفاده أن كل الأشياء الجيدة يجب أن تكون في الاعتدال ، وحتى هذا شيء "رائع" مثل الميراث. ثم بدأت أفهم مساوئ الميراث. خلصت لنفسي ، إذا كنت أرغب في إنشاء وحدات قابلة لإعادة الاستخدام ، يجب أن أجعلها أكثر استقلالية.
هندسة الإنتاج الخاصة بنا أو الاستمرار في إنشاء الدراجات
بالانتقال إلى مكان عمل جديد وبدء مشروع جديد ، أخذت في الاعتبار كل الخبرة المتاحة في تصميم الهندسة المعمارية واستمرت في إنشائها. بطبيعة الحال ، واصلت تجاهل الأبنية التي تم اختراعها بالفعل ، لكنني في الوقت نفسه التزمت بإصرار بمبدأ "فرق تسد".
لم يمض وقت طويل قبل مجيء سويفت ، لذلك بحثت في إمكانيات الهدف ج. قررت أن أفعل حقن التبعية باستخدام ميزات اللغة. لقد استلهمت من أداة توسيع lib ؛ لا أستطيع حتى تذكر اسمها.
خلاصة القول هي: في الفئة الأساسية BaseViewController ، أضفت حقل فئة BaseViewModel. وفقًا لذلك ، قمت بإنشاء وحدة التحكم الخاصة بي لكل شاشة ، ورثت تلك الأساسية ، وأضفت بروتوكولًا لوحدة التحكم للتفاعل مع viewModel. ثم جاء السحر. قمت بإعادة تعريف خصائص viewModel وإضافة دعم للبروتوكول المطلوب. في المقابل ، قمت بإنشاء فئة ViewModel جديدة لشاشة معينة تطبق هذا البروتوكول. نتيجة لذلك ، في BaseViewController في طريقة viewDidLoad ، راجعت نوع بروتوكول النموذج ، ودققت في قائمة جميع أحفاد BaseViewModel ، وعثرت على الفئة التي أحتاج إليها وقمت بإنشاء viewModel من النوع الذي احتاجه.
مثال 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
مثال ViewModel الأساسي #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
فصول المساعدة #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; }
مثال شاشة تسجيل الدخول #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
في مثل هذه الطريقة البسيطة ، فعلت التهيئة البطيئة لـ viewModel وسوء توصيل العرض بالنماذج عبر البروتوكولات. مع كل هذا ، في تلك اللحظة ، ما زلت لا أعلم شيئًا عن بنية MVP ، على الرغم من أن هناك شيئًا مشابهًا يلوح بي.
تم ترك التنقل بين الشاشات لتقدير "viewModel" ، حيث أضفت رابطًا ضعيفًا إلى وحدة التحكم.
تذكر هذا التطبيق الآن ، لا يمكنني القول بالتأكيد أن كل شيء كان سيئًا. كانت فكرة فصل الطبقات ناجحة ، وتم تبسيط لحظة إنشاء النماذج وتعيينها إلى وحدة التحكم.
لكن بالنسبة لي ، قررت أن أتعلم المزيد عن الأساليب والبنى الجاهزة ، لأنه أثناء تطوير تطبيق بهيكلتي الخاصة ، كان علي أن أتعامل مع العديد من الفروق الدقيقة. على سبيل المثال ، إعادة استخدام الشاشات والنماذج والميراث والانتقالات المعقدة بين الشاشات. في تلك اللحظة ، بدا لي أن viewModel كان جزءًا من منطق العمل ، على الرغم من أنني أدرك الآن أنه لا يزال طبقة عرض تقديمي. حصلت على تجربة رائعة خلال هذه التجربة.
MVVM مع الروح MVP
بعد أن اكتسبت خبرة بالفعل ، قررت اختيار بنية معينة لنفسي ومتابعتها ، بدلاً من اختراع الدراجات. بدأت أقرأ المزيد عن البنى ، ودرس بالتفصيل شعبية في ذلك الوقت واستقرت على MVVM. بصراحة ، لم أفهم جوهرها على الفور ، لكنني اخترتها لأنني أحببت الاسم.
لم أفهم على الفور جوهر البنية والعلاقة بين ViewModel و View (ViewController) ، لكنني بدأت أفعل ما فهمت. عيون خائفة ، والأيدي تكتب بشكل محموم الكود.
في دفاعي ، سأضيف أنه في ذلك الوقت كان وقت ووقت التفكير وتحليل الخلق الذي قمت بإنشائه ضيقًا جدًا. لذلك ، بدلاً من المجلدات ، قمت بإنشاء روابط مباشرة في ViewModel إلى العرض المقابل. وبالفعل في ViewModel نفسه ، قمت بعمل إعداد العرض التقديمي.
حول MVP ، كان لدي نفس الفكرة عن البنى الأخرى ، لذلك اعتقدت اعتقادا راسخا أنها MVVM ، حيث تحولت ViewModel إلى العروض التقديمية الأكثر واقعية.
مثال على بنائي "MVVM" ونعم ، أعجبتني الفكرة مع RootViewController ، وهو المسؤول عن أعلى مستوى من التنقل في التطبيق. حول جهاز التوجيه هو مكتوب أدناه. 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 }
لم يؤثر هذا بشكل خاص على جودة المشروع ، حيث تم احترام النظام والمقاربة الفردية. لكن التجربة كانت لا تقدر بثمن. بعد الدراجات التي قمت بإنشائها ، بدأت أخيرًا في العمل وفقًا للهندسة المعمارية المقبولة عمومًا. ما لم يتم استدعاء مقدمي العروض ، والذين قد يخلطون بين مطور تابع لجهة خارجية.
قررت أنه في المستقبل يجدر القيام بمشاريع اختبار صغيرة ، للتعمق في جوهر مقاربة معينة في التصميم بمزيد من التفصيل. إذا جاز التعبير ، أشعر أولاً بالممارسة ، ثم اقتحم المعركة. هذا هو الاستنتاج الذي توصلت إليه لنفسي.
المحاولة الثانية مع الملاحة أو جهاز التوجيه وانحناء الملاحة
في نفس المشروع ، حيث قمت بتطبيق MVVM ببسالة وسذاجة ، قررت تجربة نهج جديد في التنقل بين الشاشات. كما ذكرت سابقًا ، ما زلت متمسكًا بفكرة فصل الشاشات ومنطق الانتقال بينها.
عند قراءة MVVM ، كنت مهتمًا بنمط مثل جهاز التوجيه. بعد مراجعة الوصف مرة أخرى ، بدأت في تنفيذ الحل في مشروعي.
سبيل المثال التوجيه 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?
عدم وجود خبرة في تنفيذ مثل هذا النمط قد جعل نفسه يشعر. يبدو أن كل شيء أنيق وواضح ، أنشأ جهاز التوجيه فئة UIViewController جديدة ، وأنشأ ViewModel ونفذ منطق التبديل إلى هذه الشاشة. ولكن لا يزال ، العديد من أوجه القصور جعل أنفسهم يشعرون.
بدأت الصعوبات تنشأ عندما كان من الضروري فتح تطبيق مع شاشة معينة بعد إشعار الدفع. نتيجة لذلك ، في بعض الأماكن ، حصلنا على منطق مربك لاختيار الشاشة المناسبة وصعوبة أخرى في دعم مثل هذا النهج.
لم أتخلى عن فكرة تنفيذ جهاز التوجيه ، لكنني واصلت في هذا الاتجاه ، واكتسبت المزيد والمزيد من الخبرة. لا تتخلى عن شيء بعد أول محاولة فاشلة.
المدير المستمر
فئة مدير أخرى مثيرة للاهتمام في ممارستي. لكن هذا واحد صغير نسبيا. ومع ذلك ، تتكون عملية التطوير من التجربة والخطأ ، وبما أننا جميعًا ، أو معظمنا ، دائمًا في عملية التطوير ، تظهر الأخطاء دائمًا.
جوهر المشكلة هو هذا ، التطبيق يحتوي على الخدمات التي يجب أن يتعطل باستمرار وفي نفس الوقت يجب أن تكون متاحة في العديد من الأماكن.
مثال: تحديد حالة البلوتوث. في تطبيقي ، في عدة خدمات ، أحتاج إلى فهم ما إذا كانت تقنية Bluetooth قيد التشغيل أم لا ، والاشتراك في تحديثات الحالة. نظرًا لوجود العديد من هذه الأماكن: شاشتان ، العديد من مديري منطق الأعمال الإضافية ، وما إلى ذلك ، يصبح من الضروري لكل منهم الاشتراك في مندوب CBPeripheralManager (أو CBCentralManager).
يبدو أن الحل واضح ، فنحن ننشئ فئة منفصلة تراقب حالة البلوتوث ونبلغ كل من يحتاج إليها من خلال نمط المراقب. ولكن بعد ذلك يطرح السؤال ، من الذي سيخزن هذه الخدمة بشكل دائم؟ أول ما يتبادر إلى الذهن في هذه اللحظة هو جعله منفردًا! يبدو أن كل شيء على ما يرام!
ولكن هنا تأتي اللحظة التي تراكمت فيها أكثر من خدمة واحدة في طلبي. أنا لا أريد أن أجعل 100500 وحدة أحادية في المشروع أيضًا.
ثم ظهر ضوء آخر فوق رأسي الصغير المشرق بالفعل. اصنع منفردًا واحدًا يخزن كل هذه الخدمات ويوفر الوصول إليها عبر التطبيق. وهكذا ولد "المدير الدائم". بهذا الاسم ، لم أفكر به منذ فترة طويلة وسمّته ، حيث يمكن للجميع أن يخمنوا ، PersistentManager.
كما ترون ، لدي أيضًا مقاربة أصلية جدًا لتسمية الفصل. أعتقد أنني بحاجة إلى إضافة بدعة حول اسم الفئات في خطة التطوير الخاصة بي.
القضية الرئيسية في هذا التنفيذ هي المفرد ، والذي يتوفر في أي مكان في المشروع. وهذا يؤدي إلى حقيقة أن المديرين الذين يستخدمون واحدة من الخدمات الدائمة الوصول إليها داخل أساليبهم ، وهذا غير واضح. لأول مرة ، صادفت هذا عندما كنت أقوم بإنشاء ميزة معقدة كبيرة في مشروع تجريبي منفصل ونقل جزء من منطق العمل من المشروع الرئيسي. ثم بدأت في تلقي رسائل تتضمن أخطاء حول الخدمات المفقودة.
الاستنتاج الذي توصلت إليه بعد ذلك هو أنك بحاجة إلى تصميم الفصول الدراسية بطريقة لا توجد بها تبعيات خفية. يجب أن يتم تمرير الخدمات الضرورية كمعلمات عند تهيئة الفصل ، ولكن ليس باستخدام مفردة ، والتي يمكن الوصول إليها من أي مكان. والأكثر جمالا ، الأمر يستحق القيام به باستخدام البروتوكولات.
اتضح أن هذا تأكيد آخر على عدم وجود نمط مفرد.
يؤدي
وهذا لا يقف مكتوفي الأيدي ، لكنني أتقدم ، أتقن مناهج جديدة في البرمجة. الشيء الرئيسي هو التحرك والسعي والتجربة. الأخطاء ستكون دائما ، لا يوجد هروب من هذا. ولكن لمجرد الاعتراف بأخطاء المرء ، يمكن للمرء أن يتطور نوعيا.

في معظم الحالات ، تكون المشكلات هي الطبقات الفائقة التي تقدم الكثير ، أو التبعيات الخاطئة بين الفئات. مما يشير إلى أنه من الضروري أن تتحلل المنطق بشكل أكثر كفاءة.