تتحول معظم مشاريع 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) وإمكانية تنفيذ الطرق على مستوى البروتوكول.