نفس النوع من الأساليب؟ توقف عن كتابتها

هل سبق لك أن كتبت محولات لـ Keychain أو NSUserDefaults؟ إنها تتكون بالكامل من نفس المستوطنين والحروف. أقترح أن أكتب المنطق مرة واحدة ، مع توفير الباقي لوقت التشغيل. للتنفيذ ، أطلب القط.


لوحة المفاتيح مع أزرار النسخ واللصق


مرحبًا معك مرة أخرى vdugnist من FunCorp. في الآونة الأخيرة ، عند إضافة حقل جديد إلى محول Keychain ، اكتشفت خطأ أثناء نسخ الرمز من طريقة مجاورة.


كيف كان التنفيذ من قبل:


- (Credentials *)credentials { // implementation details } - (void)setCredentials:(Credentials *)credentials { // implementation details } - (NSDate *)firstLaunchDate { // implementation details } - (void)setFirstLaunchDate:(NSDate *)date { // implementation details } 

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


 - (Credentials *)credentials { return [self objectFromKeychainForKey:@"credentials"]; } - (void)setCredentials:(Credentials *)credentials { [self setObject:credentials toKeychainForKey:@"credentials"]; } - (NSDate *)firstLaunchDate { return [self objectFromKeychainForKey:@"firstLaunchDate"]; } - (void)setFirstLaunchDate:(NSDate *)firstLaunchDate { [self setObject:firstLaunchDate toKeychainForKey:@"firstLaunchDate"]; } - (void)setObject:(id)obj toKeychainForKey:(NSString *)key { // implementation details } - (id)objectFromKeychainForKey:(NSString *)key { // implementation details } 

أفضل بالفعل. ولكن تبقى مشكلتان أخريان:


  • لا يزال بإمكانك ختمها في ثابت السلسلة الذي نمرره إلى الطريقة ؛
  • سيتألف الفصل بأكمله من طرق مع نفس التطبيق ، وتختلف فقط في حجة الطريقة المطلوبة.

وهنا يأتي مساعدتنا. في Objective-C ، عند إضافةproperty إلى واجهة الفئة ، يتم إنشاء المُحدد و getter و ivar تلقائيًا. في تطبيق المحدد القياسي ، تتم كتابة القيمة باللغة ivar ، وبالنسبة للقارئ ، يتم قراءتها من ivar. لكي لا يتم إنشاء هذه الطرق ، في تنفيذ الفصل ، تحتاج إلى كتابة ديناميكي <اسم الحقل>. بعد ذلك ، عند الوصول إلى الحقل ، سنحصل على محدد غير معروف تم إرساله إلى استثناء المثيل.


قبل إرسال استثناء ، ستقوم الفئة +(BOOL)resolveInstanceMethod:(SEL)sel في حالة خاصية المثيل أو +(BOOL)resolveClassMethod:(SEL)sel في حالة خاصية class.
فيها ، يمكنك إضافة تطبيق الأسلوب عن طريق المحدد باستخدام class_addMethod وإرجاع YES إذا كان كل شيء class_addMethod بسلاسة. بعد ذلك ، سيتم استدعاء تطبيق الطريقة المضافة حديثًا للمكالمات الحالية واللاحقة.


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


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


 + (BOOL)resolveClassMethod:(SEL)sel { return [self resolveMethodFor:object_getClass(self) selector:sel]; } + (BOOL)resolveInstanceMethod:(SEL)sel { return [self resolveMethodFor:self selector:sel]; } + (BOOL)resolveMethodFor:(id)target selector:(SEL)sel { if (!sel_isGetterOrSetter(target, sel)) { return NO; } objc_property_t property = propertyForSelector(target, sel); if (sel_isSetter(target, sel)) { SEL getterSel = sel_getterFromSetter(sel); dvPropertySetterBlock setterBlock = [self setterBlockForTarget:target getterSelector:getterSel]; IMP blockImplementation = imp_implementationWithBlock(setterBlock); char *methodTypes = copySetterMethodTypesForProperty(property); assert(class_addMethod(target, sel, blockImplementation, methodTypes)); free(methodTypes); } else { dvPropertyGetterBlock getterBlock = [self getterBlockForTarget:target getterSelector:sel]; IMP blockImplementation = imp_implementationWithBlock(getterBlock); char *methodTypes = copyGetterMethodTypesForProperty(property); assert(class_addMethod(target, sel, blockImplementation, methodTypes)); free(methodTypes); } return YES; } + (dvPropertySetterBlock)setterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { @throw @"Override this method in subclass"; } + (dvPropertyGetterBlock)getterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { @throw @"Override this method in subclass"; } 

في الورثة ، يكفي إعادة تعريف طريقتين (كتلة getter و blockter block) ، وإضافةproperty إلى الواجهة وديناميكية للتنفيذ. هنا ، على سبيل المثال ، هو تنفيذ محول لـ NSUserDefaults:


 + (dvPropertySetterBlock)setterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { return ^(id blockSelf, id value) { [[NSUserDefaults standardUserDefaults] setObject:value forKey:NSStringFromSelector(getterSelector)]; }; } + (dvPropertyGetterBlock)getterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { return ^id(id blockSelf) { return [[NSUserDefaults standardUserDefaults] objectForKey:NSStringFromSelector(getterSelector)]; }; } 

يمكن العثور على المكتبة نفسها على github ، وأنا على استعداد للإجابة على أسئلتك في التعليقات.

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


All Articles