الأخطاء العشرة الأكثر شيوعًا التي لا يعلم مطورو iOS أنهم يرتكبونها

نشرت: 2022-03-11

ما هو الشيء الوحيد الأسوأ من رفض متجر التطبيقات لتطبيق عربات التي تجرها الدواب؟ بعد قبولها. بمجرد أن تبدأ التقييمات ذات النجمة الواحدة في الظهور ، يكاد يكون من المستحيل استردادها. هذا يكلف الشركات أموالاً والمطورين وظائفهم.

iOS هو الآن ثاني أكبر نظام تشغيل للهواتف المحمولة في العالم. كما أن لديها معدل اعتماد مرتفع للغاية ، مع أكثر من 85٪ من المستخدمين على أحدث إصدار. كما قد تتوقع ، يكون لدى المستخدمين المتفاعلين بشكل كبير توقعات عالية - إذا كان تطبيقك أو تحديثك لا تشوبه شائبة ، فسوف تسمع عنه.

مع استمرار الطلب على مطوري iOS في الارتفاع ، تحول العديد من المهندسين إلى تطوير الأجهزة المحمولة (يتم تقديم أكثر من 1000 تطبيق جديد إلى Apple كل يوم). لكن خبرة iOS الحقيقية تمتد إلى ما هو أبعد من الترميز الأساسي. فيما يلي 10 أخطاء شائعة يقع مطورو iOS فريسة لها ، وكيف يمكنك تجنبها.

يستخدم 85٪ من مستخدمي iOS أحدث إصدار من نظام التشغيل. هذا يعني أنهم يتوقعون أن يكون تطبيقك أو تحديثك خاليًا من العيوب.
سقسقة

الخطأ الشائع رقم 1: عدم فهم العمليات غير المتزامنة

هناك نوع شائع جدًا من الأخطاء بين المبرمجين الجدد وهو التعامل مع التعليمات البرمجية غير المتزامنة بشكل غير صحيح. لنفكر في سيناريو نموذجي: يفتح المستخدم شاشة مع عرض الجدول. يتم جلب بعض البيانات من الخادم وعرضها في طريقة عرض الجدول. يمكننا كتابتها بشكل أكثر رسمية:

 @property (nonatomic, strong) NSArray *dataFromServer; - (void)viewDidLoad { __weak __typeof(self) weakSelf = self; [[ApiManager shared] latestDataWithCompletionBlock:^(NSArray *newData, NSError *error){ weakSelf.dataFromServer = newData; // 1 }]; [self.tableView reloadData]; // 2 } // and other data source delegate methods - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.dataFromServer.count; }

للوهلة الأولى ، يبدو كل شيء على ما يرام: نحضر البيانات من الخادم ثم نقوم بتحديث واجهة المستخدم. ومع ذلك ، فإن المشكلة هي أن جلب البيانات عملية غير متزامنة ولن تُرجع بيانات جديدة على الفور ، مما يعني reloadData سيتم استدعاء بيانات إعادة التحميل قبل استلام البيانات الجديدة. لإصلاح هذا الخطأ ، يجب علينا نقل السطر رقم 2 بعد السطر رقم 1 داخل الكتلة مباشرة.

 @property (nonatomic, strong) NSArray *dataFromServer; - (void)viewDidLoad { __weak __typeof(self) weakSelf = self; [[ApiManager shared] latestDataWithCompletionBlock:^(NSArray *newData, NSError *error){ weakSelf.dataFromServer = newData; // 1 [weakSelf.tableView reloadData]; // 2 }]; } // and other data source delegate methods - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.dataFromServer.count; }

ومع ذلك ، قد تكون هناك مواقف لا يزال فيها هذا الرمز لا يعمل كما هو متوقع ، مما يقودنا إلى ...

الخطأ الشائع رقم 2: تشغيل التعليمات البرمجية المتعلقة بواجهة المستخدم على مؤشر ترابط آخر غير قائمة الانتظار الرئيسية

دعنا نتخيل أننا استخدمنا مثال رمز مصحح من الخطأ الشائع السابق ، لكن عرض الجدول الخاص بنا لا يزال غير محدث بالبيانات الجديدة حتى بعد اكتمال العملية غير المتزامنة بنجاح. ما الخطأ في مثل هذا الرمز البسيط؟ لفهمها ، يمكننا تعيين نقطة توقف داخل الكتلة ومعرفة قائمة الانتظار التي تسمى هذه الكتلة. هناك فرصة كبيرة لحدوث السلوك الموصوف لأن مكالمتنا ليست في قائمة الانتظار الرئيسية ، حيث يجب تنفيذ جميع التعليمات البرمجية المتعلقة بواجهة المستخدم.

تم تصميم المكتبات الأكثر شيوعًا - مثل Alamofire و AFNetworking و completionBlock - لاستدعاء CompleteBlock في قائمة الانتظار الرئيسية بعد تنفيذ مهمة غير متزامنة. ومع ذلك ، لا يمكنك الاعتماد دائمًا على هذا ومن السهل أن تنسى إرسال الكود الخاص بك إلى قائمة الانتظار الصحيحة.

للتأكد من أن جميع الأكواد المتعلقة بواجهة المستخدم موجودة في قائمة الانتظار الرئيسية ، لا تنس إرسالها إلى قائمة الانتظار هذه:

 dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView reloadData]; });

الخطأ الشائع رقم 3: سوء فهم التزامن وتعدد مؤشرات الترابط

يمكن مقارنة التزامن بسكين حاد حقًا: يمكنك بسهولة جرح نفسك إذا لم تكن حريصًا أو خبيرًا بما فيه الكفاية ، ولكنه مفيد للغاية وفعال بمجرد معرفة كيفية استخدامه بشكل صحيح وآمن.

يمكنك محاولة تجنب استخدام التزامن ، ولكن بغض النظر عن نوع التطبيقات التي تقوم بإنشائها ، فهناك فرصة كبيرة حقًا لا يمكنك الاستغناء عنها. يمكن أن يكون للتزامن فوائد كبيرة لتطبيقك. بشكل خاص:

  • يحتوي كل تطبيق تقريبًا على مكالمات لخدمات الويب (على سبيل المثال ، لإجراء بعض العمليات الحسابية الثقيلة أو قراءة البيانات من قاعدة بيانات). إذا تم تنفيذ هذه المهام في قائمة الانتظار الرئيسية ، فسيتم تجميد التطبيق لبعض الوقت ، مما يجعله غير مستجيب. علاوة على ذلك ، إذا استغرق هذا وقتًا طويلاً ، فسيغلق iOS التطبيق تمامًا. يسمح نقل هذه المهام إلى قائمة انتظار أخرى للمستخدم بمواصلة استخدام التطبيق أثناء تنفيذ العملية دون أن يظهر التطبيق متجمدًا.
  • تحتوي أجهزة iOS الحديثة على أكثر من نواة واحدة ، فلماذا ينتظر المستخدم حتى تنتهي المهام بالتتابع بينما يمكن إجراؤها بالتوازي؟

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

دعنا نفكر في بعض الأمثلة الواقعية (لاحظ أنه تم حذف بعض التعليمات البرمجية من أجل التبسيط).

حالة 1

 final class SpinLock { private var lock = OS_SPINLOCK_INIT func withLock<Return>(@noescape body: () -> Return) -> Return { OSSpinLockLock(&lock) defer { OSSpinLockUnlock(&lock) } return body() } } class ThreadSafeVar<Value> { private let lock: ReadWriteLock private var _value: Value var value: Value { get { return lock.withReadLock { return _value } } set { lock.withWriteLock { _value = newValue } } } }

الكود متعدد الخيوط:

 let counter = ThreadSafeVar<Int>(value: 0) // this code might be called from several threads counter.value += 1 if (counter.value == someValue) { // do something }

للوهلة الأولى ، تتم مزامنة كل شيء ويظهر كما لو كان يجب أن يعمل كما هو متوقع ، نظرًا لأن ThreadSaveVar يلف counter ويجعله آمنًا. لسوء الحظ ، هذا ليس صحيحًا ، حيث قد يصل counter.value == someValue أبدًا نتيجة لذلك. كحل بديل ، يمكننا أن نجعل ThreadSafeCounter يقوم بإرجاع قيمته بعد الزيادة:

 class ThreadSafeCounter { private var value: Int32 = 0 func increment() -> Int { return Int(OSAtomicIncrement32(&value)) } }

الحالة 2

 struct SynchronizedDataArray { private let synchronizationQueue = dispatch_queue_create("queue_name", nil) private var _data = [DataType]() var data: [DataType] { var dataInternal = [DataType]() dispatch_sync(self.synchronizationQueue) { dataInternal = self._data } return dataInternal } mutating func append(item: DataType) { appendItems([item]) } mutating func appendItems(items: [DataType]) { dispatch_barrier_sync(synchronizationQueue) { self._data += items } } }

في هذه الحالة ، تم استخدام dispatch_barrier_sync لمزامنة الوصول إلى المصفوفة. هذا هو النمط المفضل لضمان الوصول إلى المزامنة. لسوء الحظ ، لا يأخذ هذا الرمز في الاعتبار أن البنية تقوم بعمل نسخة في كل مرة نقوم فيها بإلحاق عنصر بها ، وبالتالي يكون لدينا قائمة انتظار مزامنة جديدة في كل مرة.

هنا ، حتى لو بدا صحيحًا للوهلة الأولى ، فقد لا يعمل كما هو متوقع. يتطلب أيضًا الكثير من العمل لاختباره وتصحيحه ، ولكن في النهاية ، يمكنك تحسين سرعة تطبيقك واستجابته.

الخطأ الشائع رقم 4: عدم معرفة مزالق الأشياء المتغيرة

يعد Swift مفيدًا جدًا في تجنب الأخطاء المتعلقة بأنواع القيم ، ولكن لا يزال هناك الكثير من المطورين الذين يستخدمون Objective-C. الأشياء المتغيرة خطيرة للغاية ويمكن أن تؤدي إلى مشاكل خفية. من المعروف جيدًا أنه يجب إرجاع الكائنات غير القابلة للتغيير من الوظائف ، لكن معظم المطورين لا يعرفون السبب. دعنا نفكر في الكود التالي:

 // Box.h @interface Box: NSObject @property (nonatomic, readonly, strong) NSArray <Box *> *boxes; @end // Box.m @interface Box() @property (nonatomic, strong) NSMutableArray <Box *> *m_boxes; - (void)addBox:(Box *)box; @end @implementation Box - (instancetype)init { self = [super init]; if (self) { _m_boxes = [NSMutableArray array]; } return self; } - (void)addBox:(Box *)box { [self.m_boxes addObject:box]; } - (NSArray *)boxes { return self.m_boxes; } @end

الكود أعلاه صحيح ، لأن NSMutableArray هو فئة فرعية من NSArray . إذن ما الخطأ الذي يمكن أن يحدث في هذا الرمز؟

الشيء الأول والأكثر وضوحًا هو أن مطورًا آخر قد يأتي ويقوم بما يلي:

 NSArray<Box *> *childBoxes = [box boxes]; if ([childBoxes isKindOfClass:[NSMutableArray class]]) { // add more boxes to childBoxes }

هذا الرمز سوف يفسد صفك. لكن في هذه الحالة ، إنها رائحة رمز ، ويترك الأمر لهذا المطور لالتقاط القطع.

ومع ذلك ، فهذه الحالة أسوأ بكثير وتدل على سلوك غير متوقع:

 Box *box = [[Box alloc] init]; NSArray<Box *> *childBoxes = [box boxes]; [box addBox:[[Box alloc] init]]; NSArray<Box *> *newChildBoxes = [box boxes];

التوقع هنا هو [newChildBoxes count] > [childBoxes count] ، لكن ماذا لو لم يكن كذلك؟ بعد ذلك ، لم يتم تصميم الفصل بشكل جيد لأنه يغير قيمة تم إرجاعها بالفعل. إذا كنت تعتقد أن عدم المساواة يجب ألا يكون صحيحًا ، فحاول تجربة UIView و [view subviews] .

لحسن الحظ ، يمكننا بسهولة إصلاح الكود الخاص بنا ، عن طريق إعادة كتابة دالة getter من المثال الأول:

 - (NSArray *)boxes { return [self.m_boxes copy]; }

الخطأ الشائع رقم 5: عدم فهم كيفية عمل iOS NSDictionary داخليًا

إذا سبق لك العمل مع فصل دراسي مخصص و NSDictionary ، فقد تدرك أنه لا يمكنك استخدام فصلك إذا كان لا يتوافق مع NSCopying قاموس. لم يسأل معظم المطورين أنفسهم أبدًا عن سبب إضافة Apple لهذا التقييد. لماذا تنسخ Apple المفتاح وتستخدم تلك النسخة بدلاً من الكائن الأصلي؟

المفتاح لفهم هذا هو معرفة كيفية عمل NSDictionary داخليًا. من الناحية الفنية ، إنه مجرد جدول تجزئة. دعنا نلخص سريعًا كيف يعمل على مستوى عالٍ أثناء إضافة كائن لمفتاح (تم حذف تغيير حجم الجدول وتحسين الأداء هنا من أجل البساطة):

الخطوة 1: يحسب hash(Key) . الخطوة 2: بناءً على التجزئة ، يبحث عن مكان لوضع الكائن. عادة ، يتم ذلك عن طريق أخذ معامل قيمة التجزئة بطول القاموس. ثم يتم استخدام الفهرس الناتج لتخزين زوج المفتاح / القيمة. الخطوة 3: إذا لم يكن هناك كائن في هذا الموقع ، فإنه ينشئ قائمة مرتبطة ويخزن سجلنا (الكائن والمفتاح). وإلا فإنه يقوم بإلحاق السجل بنهاية القائمة.

الآن ، دعنا نصف كيف يتم جلب تسجيلة من القاموس:

الخطوة 1: يحسب hash(Key) . الخطوة 2: يبحث عن مفتاح عن طريق التجزئة. في حالة عدم وجود بيانات ، لا يتم إرجاع أي nil . الخطوة 3: إذا كانت هناك قائمة مرتبطة ، فإنها تتكرر من خلال الكائن حتى [storedkey isEqual:Key] .

مع هذا الفهم لما يحدث تحت الغطاء ، يمكن استخلاص استنتاجين:

  1. إذا تغيرت تجزئة المفتاح ، فيجب نقل السجل إلى قائمة مرتبطة أخرى.
  2. يجب أن تكون المفاتيح فريدة.

دعنا نفحص هذا في فصل دراسي بسيط:

 @interface Person @property NSMutableString *name; @end @implementation Person - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[Person class]]) { return NO; } return [self.name isEqualToSting:((Person *)object).name]; } - (NSUInteger)hash { return [self.name hash]; } @end

تخيل الآن أن NSDictionary لا ينسخ المفاتيح:

 NSMutableDictionary *gotCharactersRating = [[NSMutableDictionary alloc] init]; Person *p = [[Person alloc] init]; p.name = @"Job Snow"; gotCharactersRating[p] = @10;

أوه! لدينا خطأ مطبعي هناك! دعونا نصلحها!

 p.name = @"Jon Snow";

ماذا يجب أن يحدث مع قاموسنا؟ نظرًا لتعديل الاسم ، أصبح لدينا الآن تجزئة مختلفة. الآن يقع كائننا في المكان الخطأ (لا يزال يحتوي على قيمة التجزئة القديمة ، حيث لا يعرف القاموس تغيير البيانات) ، وليس من الواضح حقًا ما الذي يجب أن نستخدمه للبحث عن البيانات في القاموس. يمكن أن تكون هناك حالة أسوأ. تخيل لو كان لدينا بالفعل "Jon Snow" في قاموسنا مع تصنيف 5. سينتهي القاموس بقيمتين مختلفتين لنفس المفتاح.

كما ترى ، هناك العديد من المشاكل التي يمكن أن تنشأ من وجود مفاتيح قابلة للتغيير في NSDictionary . أفضل ممارسة لتجنب مثل هذه المشكلات هي نسخ الكائن قبل تخزينه ، ووضع علامة على الخصائص copy . ستساعدك هذه الممارسة أيضًا في الحفاظ على اتساق فصلك.

الخطأ الشائع رقم 6: استخدام القصص المصورة بدلاً من XIBs

يتبع معظم مطوري iOS الجدد اقتراح Apple ويستخدمون القصص المصورة افتراضيًا لواجهة المستخدم. ومع ذلك ، هناك الكثير من العيوب وفقط عدد قليل من المزايا (القابلة للنقاش) في استخدام القصص المصورة.

تشمل عيوب القصة المصورة ما يلي:

  1. من الصعب حقًا تعديل لوحة العمل للعديد من أعضاء الفريق. من الناحية الفنية ، يمكنك استخدام العديد من القصص المصورة ، ولكن الميزة الوحيدة ، في هذه الحالة ، هي إتاحة إمكانية وجود مقاطع بين وحدات التحكم على لوحة العمل.
  2. عناصر التحكم وأسماء المقاطع من القصص المصورة عبارة عن سلاسل ، لذلك عليك إما إعادة إدخال كل هذه السلاسل عبر التعليمات البرمجية (وفي يوم من الأيام ستكسرها ) ، أو الاحتفاظ بقائمة ضخمة من ثوابت لوحة العمل. يمكنك استخدام SBConstants ، لكن إعادة التسمية على لوحة العمل لا تزال مهمة صعبة.
  3. تجبرك القصص المصورة على تصميم غير معياري. أثناء العمل باستخدام لوحة العمل ، يكون هناك القليل جدًا من الحافز لجعل وجهات نظرك قابلة لإعادة الاستخدام. قد يكون هذا مقبولاً للحد الأدنى من المنتج القابل للتطبيق (MVP) أو النماذج الأولية السريعة لواجهة المستخدم ، ولكن في التطبيقات الحقيقية ، قد تحتاج إلى استخدام نفس العرض عدة مرات عبر تطبيقك.

مزايا القصة المصورة (قابلة للنقاش):

  1. يمكن رؤية التنقل في التطبيق بالكامل في لمحة. ومع ذلك ، يمكن أن تحتوي التطبيقات الحقيقية على أكثر من عشرة وحدات تحكم متصلة في اتجاهات مختلفة. تبدو القصص المصورة التي تحتوي على مثل هذه الاتصالات وكأنها كرة من الغزل ولا تعطي أي فهم عالي المستوى لتدفقات البيانات.
  2. جداول ثابتة. هذه هي الميزة الحقيقية الوحيدة التي يمكنني التفكير فيها. تكمن المشكلة في أن 90 بالمائة من الجداول الثابتة تميل إلى التحول إلى جداول ديناميكية أثناء تطور التطبيق ويمكن التعامل مع الجدول الديناميكي بسهولة أكبر بواسطة XIBs.

الخطأ الشائع رقم 7: مقارنة الكائن والمؤشر المربك

أثناء مقارنة كائنين ، يمكننا اعتبار مساوتين: المساواة بين المؤشر والكائن.

مساواة المؤشر هي حالة عندما يشير كلا المؤشرين إلى نفس الكائن. في Objective-C ، نستخدم عامل التشغيل == لمقارنة مؤشرين. تساوي الكائن هو الموقف الذي يمثل فيه كائنان كائنين متطابقين منطقيًا ، مثل نفس المستخدم من قاعدة بيانات. في Objective-C ، نستخدم isEqual أو الأفضل من النوع المحدد isEqualToString و isEqualToDate وما إلى ذلك لمقارنة كائنين.

ضع في اعتبارك الكود التالي:

 NSString *a = @"a"; // 1 NSString *b = @"a"; // 2 if (a == b) { // 3 NSLog(@"%@ is equal to %@", a, b); } else { NSLog(@"%@ is NOT equal to %@", a, b); }

ما الذي سيتم طباعته على وحدة التحكم عند تشغيل هذا الرمز؟ سنحصل على a is equal to b ، لأن كلا الكائنين a و b يشيران إلى نفس الشيء في الذاكرة.

لكن دعنا الآن نغير السطر 2 إلى:

 NSString *b = [[@"a" mutableCopy] copy];

الآن نحصل على a is NOT equal to b لأن هذه المؤشرات تشير الآن إلى كائنات مختلفة على الرغم من أن هذه الكائنات لها نفس القيم.

يمكن تجنب هذه المشكلة بالاعتماد على isEqual أو نوع وظائف معينة. في مثال الكود الخاص بنا ، يجب أن نستبدل السطر 3 بالكود التالي لكي يعمل دائمًا بشكل صحيح:

 if ([a isEqual:b]) {

الخطأ الشائع رقم 8: استخدام القيم المشفرة

هناك مشكلتان أساسيتان في القيم المشفرة:

  1. غالبًا ما يكون من غير الواضح ما يمثلونه.
  2. يجب إعادة إدخالها (أو نسخها ولصقها) عندما يلزم استخدامها في أماكن متعددة في الكود.

ضع في اعتبارك المثال التالي:

 if ([[NSDate date] timeIntervalSinceDate:self.lastAppLaunch] < 172800) { // do something } or [self.tableView registerNib:nib forCellReuseIdentifier:@"SimpleCell"]; ... [self.tableView dequeueReusableCellWithIdentifier:@"SimpleCell"];

ماذا يمثل 172800؟ لماذا يتم استخدامه؟ ربما ليس من الواضح أن هذا يتوافق مع عدد الثواني في يومين (هناك 24 × 60 × 60 ، أو 86400 ثانية في اليوم).

بدلاً من استخدام القيم ذات الترميز الثابت ، يمكنك تحديد قيمة باستخدام العبارة #define . علي سبيل المثال:

 #define SECONDS_PER_DAY 86400 #define SIMPLE_CELL_IDENTIFIER @"SimpleCell"

#define هو ماكرو معالج مسبق يستبدل التعريف المسمى بقيمته في الكود. لذلك ، إذا كان لديك #define في ملف رأس وقمت باستيراده في مكان ما ، فسيتم استبدال جميع تكرارات القيمة المحددة في هذا الملف أيضًا.

هذا يعمل بشكل جيد ، باستثناء قضية واحدة. لتوضيح المشكلة المتبقية ، ضع في اعتبارك الكود التالي:

 #define X = 3 ... CGFloat y = X / 2;

ماذا تتوقع أن تكون قيمة y بعد تنفيذ هذا الرمز؟ إذا قلت 1.5 ، فأنت غير صحيح. ستكون y مساوية لـ 1 ( وليس 1.5) بعد تنفيذ هذا الرمز. لماذا ا؟ الجواب هو أن #define ليس لديه معلومات عن النوع. لذلك ، في حالتنا ، لدينا قسمة على قيمتين Int (3 و 2) ، مما ينتج عنه Int (أي 1) والذي يتم طرحه بعد ذلك على Float .

يمكن تجنب ذلك باستخدام الثوابت بدلاً من ذلك ، والتي يتم كتابتها حسب التعريف:

 static const CGFloat X = 3; ... CGFloat y = X / 2; // y will now equal 1.5, as expected

الخطأ الشائع رقم 9: استخدام الكلمة الرئيسية الافتراضية في بيان التبديل

يمكن أن يؤدي استخدام الكلمة الأساسية default في بيان التبديل إلى أخطاء وسلوك غير متوقع. ضع في اعتبارك الكود التالي في Objective-C:

 typedef NS_ENUM(NSUInteger, UserType) { UserTypeAdmin, UserTypeRegular }; - (BOOL)canEditUserWithType:(UserType)userType { switch (userType) { case UserTypeAdmin: return YES; default: return NO; } }

نفس الكود المكتوب بلغة Swift:

 enum UserType { case Admin, Regular } func canEditUserWithType(type: UserType) -> Bool { switch(type) { case .Admin: return true default: return false } }

يعمل هذا الرمز على النحو المنشود ، مما يسمح للمستخدمين المسؤولين فقط بالقدرة على تغيير السجلات الأخرى. ومع ذلك ، ما الذي قد يحدث عندما نضيف نوع مستخدم آخر ، "مدير" ، والذي يجب أن يكون قادرًا على تحرير السجلات أيضًا؟ إذا نسينا تحديث بيان switch هذا ، فسيتم تجميع الكود ، لكنه لن يعمل كما هو متوقع. ومع ذلك ، إذا استخدم المطور قيم التعداد بدلاً من الكلمة الأساسية الافتراضية من البداية ، فسيتم تحديد الإشراف في وقت التجميع ، ويمكن إصلاحه قبل الذهاب للاختبار أو الإنتاج. إليك طريقة جيدة للتعامل مع هذا في Objective-C:

 typedef NS_ENUM(NSUInteger, UserType) { UserTypeAdmin, UserTypeRegular, UserTypeManager }; - (BOOL)canEditUserWithType:(UserType)userType { switch (userType) { case UserTypeAdmin: case UserTypeManager: return YES; case UserTypeRegular: return NO; } }

نفس الكود المكتوب بلغة Swift:

 enum UserType { case Admin, Regular, Manager } func canEditUserWithType(type: UserType) -> Bool { switch(type) { case .Manager: fallthrough case .Admin: return true case .Regular: return false } }

الخطأ الشائع رقم 10: استخدام NSLog

يستخدم العديد من مطوري iOS NSLog في تطبيقاتهم للتسجيل ، ولكن في معظم الأحيان يكون هذا خطأ فادحًا. إذا تحققنا من وثائق Apple لوصف وظيفة NSLog ، فسنرى أنه بسيط للغاية:

 void NSLog(NSString *format, ...);

ما الذي يمكن أن يحدث خطأ في ذلك؟ في الحقيقة ، لا شيء. ومع ذلك ، إذا قمت بتوصيل جهازك بمنظم Xcode ، فسترى جميع رسائل تصحيح الأخطاء الخاصة بك هناك. لهذا السبب وحده ، لا يجب أبدًا استخدام NSLog للتسجيل: من السهل إظهار بعض البيانات الداخلية غير المرغوب فيها ، بالإضافة إلى أنها تبدو غير مهنية.

النهج الأفضل هو استبدال NSLogs بـ CocoaLumberjack القابل للتكوين أو بعض أطر عمل التسجيل الأخرى.

يتم إحتوائه

iOS هو نظام أساسي قوي للغاية وسريع التطور. تبذل Apple جهدًا مستمرًا هائلاً لتقديم أجهزة وميزات جديدة لنظام iOS نفسه ، مع توسيع لغة Swift باستمرار.

سيؤدي تحسين مهاراتك في Objective-C و Swift إلى جعلك مطور iOS رائعًا ويوفر فرصًا للعمل في المشاريع الصعبة باستخدام أحدث التقنيات.

الموضوعات ذات الصلة: دليل مطور iOS: من Objective-C إلى Learning Swift