كيفية الكتابة على الهدف جيم في 2018. الجزء الأول

تتحول معظم مشاريع iOS جزئيًا أو كليًا إلى Swift. تعد Swift لغة رائعة ، ومعها يكمن مستقبل تطوير iOS. لكن اللغة مرتبطة ارتباطًا وثيقًا بمجموعة الأدوات ، وهناك عيوب في مجموعة أدوات Swift.


لا يزال المترجم Swift لديه أخطاء تتسبب في تعطله أو إنشاء رمز غير صحيح. لا يحتوي Swift على ABI مستقر. والأهم من ذلك ، أن مشاريع Swift مستمرة منذ فترة طويلة.


وفي هذا الصدد ، قد تكون المشاريع القائمة أكثر ربحية لمواصلة التطوير في الهدف جيم. والهدف- C ليس كما كان عليه من قبل!


في هذه السلسلة من المقالات ، سوف نعرض لك الميزات والتحسينات المفيدة في Objective-C ، والتي يصبح من الأفضل لك بها كتابة التعليمات البرمجية. كل من يكتب في Objective-C سيجد شيئًا مثيرًا للاهتمام بالنسبة له.



let و var


لم يعد Objective-C بحاجة إلى تحديد أنواع متغيرة بشكل صريح: في Xcode 8 ، ظهر امتداد لغة __auto_type ، وقبل أن يكون استدلال نوع Xcode 8 متاحًا في Objective-C ++ (باستخدام الكلمة الأساسية auto مع ظهور C ++ 0X).


أولاً ، let نضيف وحدات الماكرو let and var :


 #define let __auto_type const #define var __auto_type 

 //  NSArray<NSString *> *const items = [string componentsSeparatedByString:@","]; void(^const completion)(NSData * _Nullable, NSURLResponse * _Nullable, NSError * _Nullable) = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { // ... }; //  let items = [string componentsSeparatedByString:@","]; let completion = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { // ... }; 

إذا كنت تستخدم كتابة const بعد مؤشر إلى فئة Objective-C ، فقد كان ترفا غير مقبول ، ولكن الآن تم اعتبار الإعلان الضمني عن const (عبر let ) أمرا مفروغا منه. الفرق ملحوظ بشكل خاص عند حفظ كتلة إلى متغير.


لأنفسنا ، قمنا بتطوير قاعدة لاستخدام let ومتغير لإعلان جميع المتغيرات. حتى عندما تتم تهيئة المتغير إلى nil :


 - (nullable JMSomeResult *)doSomething { var result = (JMSomeResult *)nil; if (...) { result = ...; } return result; } 

الاستثناء الوحيد هو عندما تحتاج إلى التأكد من تعيين قيمة للمتغير في كل فرع رمز:


 NSString *value; if (...) { if (...) { value = ...; } else { value = ...; } } else { value = ...; } 

بهذه الطريقة فقط سوف نحصل على تحذير مترجم إذا نسينا تعيين قيمة لأحد الفروع.


وأخيرًا: لاستخدام let و var لمتغيرات id type ، تحتاج إلى تعطيل تحذير auto-var-id (add -Wno-auto-var-id إلى "Other Warning Flags" في إعدادات المشروع).


نوع الإرجاع التلقائي لنوع الكتلة


قليل من الناس يعرفون أن المترجم يمكن أن يستنتج نوع القيمة المرجعة للكتلة:


 let block = ^{ return @"abc"; }; // `block`   `NSString *(^const)(void)` 

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


  1. إذا كان هناك العديد من return في كتلة تُرجع قيمًا من أنواع مختلفة.


     let block1 = ^NSUInteger(NSUInteger value){ if (value > 0) { return value; } else { // `NSNotFound`   `NSInteger` return NSNotFound; } }; let block2 = ^JMSomeBaseClass *(BOOL flag) { if (flag) { return [[JMSomeBaseClass alloc] init]; } else { // `JMSomeDerivedClass`   `JMSomeBaseClass` return [[JMSomeDerivedClass alloc] init]; } }; 

  2. إذا كان هناك return في الكتلة يرجع nil .


     let block1 = ^NSString * _Nullable(){ return nil; }; let block2 = ^NSString * _Nullable(BOOL flag) { if (flag) { return @"abc"; } else { return nil; } }; 

  3. إذا كان يجب أن تعود الكتلة BOOL .


     let predicate = ^BOOL(NSInteger lhs, NSInteger rhs){ return lhs > rhs; }; 


التعبيرات التي تحتوي على عامل مقارنة في C (وبالتالي في Objective-C) هي من النوع int . لذلك ، من الأفضل جعلها قاعدة لتحديد نوع الإرجاع BOOL دائمًا بشكل صريح.


الوراثة و for...in


في Xcode 7 ، ظهرت الأدوية العامة (أو بالأحرى الأدوية الخفيفة) في Objective-C. نأمل أن تستخدمها بالفعل. ولكن إذا لم يكن كذلك ، يمكنك مشاهدة جلسة WWDC أو قراءتها هنا أو هنا .


لأنفسنا ، قمنا بتطوير قاعدة لتحديد المعلمات العامة دائمًا ، حتى لو كانت id ( NSArray<id> * ). وبالتالي ، من السهل التمييز بين الشفرة القديمة التي لم يتم تحديد المعلمات العامة فيها بعد.


مع وحدات الماكرو let and var ، نتوقع أن نتمكن من استخدامها for...in حلقة for...in :


 let items = (NSArray<NSString *> *)@[@"a", @"b", @"c"]; for (let item in items) { NSLog(@"%@", item); } 

لكن هذا الرمز لا يجمع. على الأرجح ، __auto_type يكن __auto_type مدعومًا for...in ، لأنه for...in يعمل فقط مع المجموعات التي تنفذ بروتوكول NSFastEnumeration . وبالنسبة للبروتوكولات في الهدف جيم ، لا يوجد دعم للأدوية العامة.


لإصلاح هذا العيب ، حاول أن تجعل الماكرو foreach الخاص بك. أول شيء يتبادر إلى الذهن: جميع المجموعات في Foundation لها خاصية objectEnumerator ، وقد يبدو الماكرو كما يلي:


 #define foreach(object_, collection_) \ for (typeof([(collection_).objectEnumerator nextObject]) object_ in (collection_)) 

ولكن بالنسبة لـ NSDictionary و NSMapTable طريقة بروتوكول NSMapTable NSFastEnumeration عبر المفاتيح ، وليس القيم (ستحتاج إلى استخدام keyEnumerator ، وليس objectEnumerator ).


سنحتاج إلى الإعلان عن خاصية جديدة سيتم استخدامها فقط للحصول على النوع في تعبير typeof :


 @interface NSArray<__covariant ObjectType> (ForeachSupport) @property (nonatomic, strong, readonly) ObjectType jm_enumeratedType; @end @interface NSDictionary<__covariant KeyType, __covariant ObjectType> (ForeachSupport) @property (nonatomic, strong, readonly) KeyType jm_enumeratedType; @end #define foreach(object_, collection_) \ for (typeof((collection_).jm_enumeratedType) object_ in (collection_)) 

الآن كودنا يبدو أفضل بكثير:


 //  for (MyItemClass *item in items) { NSLog(@"%@", item); } //  foreach (item, items) { NSLog(@"%@", item); } 

مقتطف لـ Xcode
 foreach (<#object#>, <#collection#>) { <#statements#> } 

copy / mutableCopy


مكان آخر حيث لا تتوفر فيه الكتابة في Objective-C هو -mutableCopy و -mutableCopy (وكذلك -copyWithZone: and -mutableCopyWithZone: طرق ، لكننا لا ندعوها مباشرة).


لتجنب الحاجة إلى القوالب الواضحة ، يمكنك إعادة تعريف الطرق بنوع الإرجاع. على سبيل المثال ، بالنسبة لـ NSArray ستكون الإعلانات:


 @interface NSArray<__covariant ObjectType> (TypedCopying) - (NSArray<ObjectType> *)copy; - (NSMutableArray<ObjectType> *)mutableCopy; @end 

 let items = [NSMutableArray<NSString *> array]; // ... //  let itemsCopy = (NSArray<NSString *> *)[items copy]; //  let itemsCopy = [items copy]; 

warn_unused_result


نظرًا لأننا قد أعدنا الإعلان عن -mutableCopy والنسخ -mutableCopy ، فسيكون من الرائع ضمان استخدام نتيجة استدعاء هذه الأساليب. للقيام بذلك ، يحتوي warn_unused_result السمة warn_unused_result .


 #define JM_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) 

 @interface NSArray<__covariant ObjectType> (TypedCopying) - (NSArray<ObjectType> *)copy JM_WARN_UNUSED_RESULT; - (NSMutableArray<ObjectType> *)mutableCopy JM_WARN_UNUSED_RESULT; @end 

بالنسبة للرمز التالي ، سيقوم المحول البرمجي بإنشاء تحذير:


 let items = @[@"a", @"b", @"c"]; [items mutableCopy]; // Warning: Ignoring return value of function declared with 'warn_unused_result' attribute. 

overloadable


يعرف القليل أن Clang يسمح لك بإعادة تعريف الوظائف في لغة C (وبالتالي في Objective-C). باستخدام السمة overloadable ، يمكنك إنشاء وظائف بنفس الاسم ، ولكن مع أنواع مختلفة من الوسيطات أو بأرقام مختلفة.


لا يمكن أن تختلف الوظائف القابلة للتجاوز في نوع القيمة المرتجعة فقط.


 #define JM_OVERLOADABLE __attribute__((overloadable)) 

 JM_OVERLOADABLE float JMCompare(float lhs, float rhs); JM_OVERLOADABLE float JMCompare(float lhs, float rhs, float accuracy); JM_OVERLOADABLE double JMCompare(double lhs, double rhs); JM_OVERLOADABLE double JMCompare(double lhs, double rhs, double accuracy); 

التعبيرات المعبأة


مرة أخرى في عام 2012 ، في جلسة WWDC 413 ، قدمت شركة Apple حرفية لـ NSArray و NSDictionary و NSDictionary ، بالإضافة إلى التعبيرات المعبأة. يمكنك قراءة المزيد عن الحرفي والتعابير المعبأة في وثائق Clang .


 //  @YES // [NSNumber numberWithBool:YES] @NO // [NSNumber numberWithBool:NO] @123 // [NSNumber numberWithInt:123] @3.14 // [NSNumber numberWithDouble:3.14] @[obj1, obj2] // [NSArray arrayWithObjects:obj1, obj2, nil] @{key1: obj1, key2: obj2} // [NSDictionary dictionaryWithObjectsAndKeys:obj1, key1, obj2, key2, nil] // Boxed expressions @(boolVariable) // [NSNumber numberWithBool:boolVariable] @(intVariable) // [NSNumber numberWithInt:intVariable)] 

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


 //  `NSDirectionalEdgeInsets`  `NSValue` let insets = (NSDirectionalEdgeInsets){ ... }; let value = [[NSValue alloc] initWithBytes:&insets objCType:@encode(typeof(insets))]; // ... //  `NSDirectionalEdgeInsets`  `NSValue` var insets = (NSDirectionalEdgeInsets){}; [value getValue:&insets]; 

يتم تحديد الطرق والخصائص المساعدة لبعض الفئات (مثل طريقة +[NSValue valueWithCGPoint:] وخصائص CGPointValue ) ، ولكن هذا لا يزال غير ملائم مثل التعبير CGPointValue !


وفي عام 2015 ، قام Alex Denisov بعمل تصحيح لـ Clang ، مما يسمح لك باستخدام التعبيرات المعبأة لف أي هياكل في NSValue .


لكي تدعم بنيتنا التعبيرات المعبأة ، تحتاج فقط إلى إضافة سمة objc_boxable للهيكل.


 #define JM_BOXABLE __attribute__((objc_boxable)) 

 typedef struct JM_BOXABLE JMDimension { JMDimensionUnit unit; CGFloat value; } JMDimension; 

ويمكننا استخدام بناء الجملة @(...) لبنيتنا:


 let dimension = (JMDimension){ ... }; let boxedValue = @(dimension); //   `NSValue *` 

لا يزال يتعين عليك استعادة البنية من خلال الطريقة -[NSValue getValue:] أو طريقة الفئة.


تحدد CG_BOXABLE الماكرو الخاص بها ، CG_BOXABLE ، والتعبيرات المعبأة مدعومة بالفعل CGSize CGVector و CGRect و CGRect و CGRect .


بالنسبة للهياكل الأخرى شائعة الاستخدام ، يمكننا إضافة دعم للتعبيرات المعبأة بمفردنا:


 typedef struct JM_BOXABLE _NSRange NSRange; typedef struct JM_BOXABLE CGAffineTransform CGAffineTransform; typedef struct JM_BOXABLE UIEdgeInsets UIEdgeInsets; typedef struct JM_BOXABLE NSDirectionalEdgeInsets NSDirectionalEdgeInsets; typedef struct JM_BOXABLE UIOffset UIOffset; typedef struct JM_BOXABLE CATransform3D CATransform3D; 

الحرف المركبة


بناء لغة مفيد آخر هو المركب الحرفي . ظهرت الحرف المركب في دول مجلس التعاون الخليجي كملحق لغوي ، وتمت إضافته لاحقًا إلى معيار C11.


إذا كان في وقت سابق ، بعد تلبية المكالمة إلى UIEdgeInsetsMake ، يمكننا فقط تخمين ما هي المسافة البادئة التي سنحصل عليها (كان علينا مشاهدة إعلان وظيفة UIEdgeInsetsMake ) ، ثم UIEdgeInsetsMake المركب يتحدث الرمز عن نفسه:


 //  UIEdgeInsetsMake(1, 2, 3, 4) //  (UIEdgeInsets){ .top = 1, .left = 2, .bottom = 3, .right = 4 } 

إنه أكثر ملاءمة لاستخدام مثل هذا البناء عندما تكون بعض الحقول صفرًا:


 (CGPoint){ .y = 10 } //  (CGPoint){ .x = 0, .y = 10 } (CGRect){ .size = { .width = 10, .height = 20 } } //  (CGRect){ .origin = { .x = 0, .y = 0 }, .size = { .width = 10, .height = 20 } } (UIEdgeInsets){ .top = 10, .bottom = 20 } //  (UIEdgeInsets){ .top = 20, .left = 0, .bottom = 10, .right = 0 } 

بالطبع ، في الحرف الحرفي المركب ، لا يمكنك استخدام الثوابت فقط ، ولكن أيضًا أي تعبيرات:


 textFrame = (CGRect){ .origin = { .y = CGRectGetMaxY(buttonFrame) + textMarginTop }, .size = textSize }; 

مقتطفات لـ Xcode
 (NSRange){ .location = <#location#>, .length = <#length#> } (CGPoint){ .x = <#x#>, .y = <#y#> } (CGSize){ .width = <#width#>, .height = <#height#> } (CGRect){ .origin = { .x = <#x#>, .y = <#y#> }, .size = { .width = <#width#>, .height = <#height#> } } (UIEdgeInsets){ .top = <#top#>, .left = <#left#>, .bottom = <#bottom#>, .right = <#right#> } (NSDirectionalEdgeInsets){ .top = <#top#>, .leading = <#leading#>, .bottom = <#bottom#>, .trailing = <#trailing#> } (UIOffset){ .horizontal = <#horizontal#>, .vertical = <#vertical#> } 

إبطال


في Xcode 6.3.2 ، ظهرت التعليقات التوضيحية للإبطال في Objective-C. أضافهم مطورو Apple لاستيراد Objective-C API إلى Swift. ولكن إذا تمت إضافة شيء إلى اللغة ، فيجب عليك محاولة وضعه في خدمتك. وسنشرح كيف نستخدم البطلان في مشروع Objective-C وما هي القيود.


لتحديث معلوماتك ، يمكنك مشاهدة جلسة WWDC .


أول شيء فعلناه هو البدء في كتابة NS_ASSUME_NONNULL_END / NS_ASSUME_NONNULL_END في جميع ملفات NS_ASSUME_NONNULL_END . من أجل عدم القيام بذلك يدويًا ، نقوم بتصحيح قوالب الملفات مباشرة في Xcode.


بدأنا أيضًا في تعيين الإبطال بشكل صحيح لجميع الخصائص والأساليب الخاصة.


إذا أضفنا وحدات الماكرو NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END إلى ملف NS_ASSUME_NONNULL_END موجود ، فإننا نضيف على الفور الملفات null_resettable و _Nullable و _Nullable في الملف بأكمله.


يتم تمكين جميع تحذيرات المحول البرمجي المفيدة بشكل افتراضي. ولكن هناك تحذير متطرف واحد أود تضمينه: -Wnullable-to-nonnull-conversion (تم تعيينه في "علامات التحذير الأخرى" في إعدادات المشروع). ينشئ المحول البرمجي هذا التحذير عندما يتم تحويل متغير أو تعبير من نوع فارغ إلى ضمنيًا إلى نوع غير فارغ.


 + (NSString *)foo:(nullable NSString *)string { return string; // Implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' } 

لسوء الحظ ، بالنسبة إلى __auto_type (وبالتالي let __auto_type ) ، لا يعمل هذا التحذير. النوع المستنتج عبر __auto_type يتجاهل التعليق التوضيحي __auto_type . وبناءً على تعليق مطور Apple في rdar: // 27062504 ، فإن هذا السلوك لن يتغير. وقد لوحظ تجريبيًا أن إضافة _Nullable أو _Nonnull إلى __auto_type لا يؤثر على أي شيء.


 - (NSString *)test:(nullable NSString *)string { let tmp = string; return tmp; //   } 

لقمع nullable-to-nonnull-conversion الذي لا يمكن nullable-to-nonnull-conversion كتبنا ماكرو "يفرض الإلغاء". فكرة مأخوذة من الماكرو RBBNotNil . ولكن بسبب سلوك __auto_type من التخلص من الطبقة المساعدة.


 #define JMNonnull(obj_) \ ({ \ NSCAssert(obj_, @"Expected `%@` not to be nil.", @#obj_); \ (typeof({ __auto_type result_ = (obj_); result_; }))(obj_); \ }) 

مثال على استخدام الماكرو JMNonnull :


 @interface JMRobot : NSObject @property (nonatomic, strong, nullable) JMLeg *leftLeg; @property (nonatomic, strong, nullable) JMLeg *rightLeg; @end @implementation JMRobot - (void)stepLeft { [self step:JMNonnull(self.leftLeg)] } - (void)stepRight { [self step:JMNonnull(self.rightLeg)] } - (void)step:(JMLeg *)leg { // ... } @end 

لاحظ أنه في وقت كتابة هذا التقرير ، لا يعمل تحذير nullable-to-nonnull-conversion : لم يفهم المترجم بعد أن المتغير nonnull ، بعد التحقق من عدم المساواة ، يمكن تفسير nil على أنه nonnull .


 - (NSString *)foo:(nullable NSString *)string { if (string != nil) { return string; // Implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' } else { return @""; } } 

في كود Objective-C ++ ، يمكنك تجاوز هذا القيد باستخدام بناء if let ، لأن Objective-C ++ يسمح بتعريفات متغيرة في التعبير عن عبارة if .


 - (NSString *)foo:(nullable NSString *)stringOrNil { if (let string = stringOrNil) { return string; } else { return @""; } } 

روابط مفيدة


هناك أيضًا عدد من وحدات الماكرو والكلمات الرئيسية الأكثر شهرة التي أود أن أذكرها: الكلمة الرئيسية @available ، ووحدات الماكرو NS_DESIGNATED_INITIALIZER ، NS_UNAVAILABLE ، NS_REQUIRES_SUPER ، NS_NOESCAPE ، NS_ENUM ، NS_OPTIONS (أو وحدات الماكرو لنفس السمات) ومكتبةkeypath. نوصي أيضًا بإلقاء نظرة على بقية مكتبة libextobjc .



→ يتم نشر رمز المقال في جوهره .


الخاتمة


في الجزء الأول من المقال ، حاولنا التحدث عن الميزات الرئيسية والتحسينات اللغوية البسيطة ، والتي تسهل إلى حد كبير كتابة ودعم كود Objective-C. في الجزء التالي ، سنوضح كيف يمكنك زيادة إنتاجيتك بمساعدة التعدادات كما هو الحال في Swift (وهي فئات الحالة ؛ فهي أنواع البيانات الجبرية ، ADTs) وإمكانية تنفيذ الطرق على مستوى البروتوكول.

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


All Articles