تتحول معظم مشاريع 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
إذا كنت تستخدم كتابة 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"; };
إنه مريح للغاية. خاصة إذا كتبت رمزًا تفاعليًا باستخدام ReactiveObjC . ولكن هناك عدد من القيود التي يجب عليك بموجبها تحديد نوع القيمة المرتجعة بشكل صريح.
إذا كان هناك العديد من return في كتلة تُرجع قيمًا من أنواع مختلفة.
let block1 = ^NSUInteger(NSUInteger value){ if (value > 0) { return value; } else {
إذا كان هناك return في الكتلة يرجع nil .
let block1 = ^NSString * _Nullable(){ return nil; }; let block2 = ^NSString * _Nullable(BOOL flag) { if (flag) { return @"abc"; } else { return nil; } };
إذا كان يجب أن تعود الكتلة 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_))
الآن كودنا يبدو أفضل بكثير:
مقتطف لـ 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];
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];
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 .
باستخدام الحرف والتعبيرات المعبأة ، يمكنك بسهولة الحصول على كائن يمثل رقمًا أو قيمة منطقية. ولكن للحصول على كائن يلف الهيكل ، تحتاج إلى كتابة بعض التعليمات البرمجية:
يتم تحديد الطرق والخصائص المساعدة لبعض الفئات (مثل طريقة +[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 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 المركب يتحدث الرمز عن نفسه:
إنه أكثر ملاءمة لاستخدام مثل هذا البناء عندما تكون بعض الحقول صفرًا:
(CGPoint){ .y = 10 }
بالطبع ، في الحرف الحرفي المركب ، لا يمكنك استخدام الثوابت فقط ، ولكن أيضًا أي تعبيرات:
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;
لسوء الحظ ، بالنسبة إلى __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 {
لاحظ أنه في وقت كتابة هذا التقرير ، لا يعمل تحذير nullable-to-nonnull-conversion : لم يفهم المترجم بعد أن المتغير nonnull ، بعد التحقق من عدم المساواة ، يمكن تفسير nil على أنه nonnull .
- (NSString *)foo:(nullable NSString *)string { if (string != nil) { return string;
في كود 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) وإمكانية تنفيذ الطرق على مستوى البروتوكول.