मुझे लगता है कि यह वास्तव में बहुत अच्छा सवाल है, और यह शर्म की बात है कि असली सवाल से निपटने के बजाय, अधिकांश जवाबों ने इस मुद्दे को छोड़ दिया है और बस कहा कि स्विज़लिंग का उपयोग न करें।
सिज़लिंग विधि का उपयोग करना रसोईघर में तेज चाकू का उपयोग करने जैसा है। कुछ लोग तेज चाकू से डरते हैं क्योंकि उन्हें लगता है कि वे खुद को बुरी तरह से काट लेंगे, लेकिन सच्चाई यह है कि तेज चाकू सुरक्षित हैं ।
बेहतर, अधिक कुशल, अधिक अनुरक्षण कोड लिखने के लिए विधि स्विज़लिंग का उपयोग किया जा सकता है। इसका दुरुपयोग भी हो सकता है और भयानक कीड़े हो सकते हैं।
पृष्ठभूमि
सभी डिज़ाइन पैटर्न के साथ, यदि हम पैटर्न के परिणामों से पूरी तरह अवगत हैं, तो हम इसका उपयोग करने या न करने के बारे में अधिक सूचित निर्णय लेने में सक्षम हैं। सिंगलटन एक ऐसी चीज़ का एक अच्छा उदाहरण है जो बहुत विवादास्पद है, और अच्छे कारण के लिए - वे वास्तव में ठीक से लागू करने के लिए कठिन हैं। कई लोग अभी भी सिंगलटन का उपयोग करना चुनते हैं, हालाँकि। यही बात स्वाज़लिंग के बारे में भी कही जा सकती है। एक बार अच्छी और बुरी दोनों बातों को समझने के बाद आपको अपनी राय बनानी चाहिए।
विचार-विमर्श
यहाँ विधि स्वाइलिंग के कुछ नुकसान हैं:
- विधि स्वाज़लिंग परमाणु नहीं है
- गैर-स्वामित्व वाले कोड का व्यवहार बदलता है
- संभव नामकरण संघर्ष
- स्विज़लिंग से विधि की दलीलें बदल जाती हैं
- स्विज़ल्स का क्रम मायने रखता है
- समझने में मुश्किल (पुनरावर्ती दिखता है)
- डिबग करना मुश्किल है
ये बिंदु सभी मान्य हैं, और उन्हें संबोधित करते हुए हम परिणाम को प्राप्त करने के लिए उपयोग की जाने वाली पद्धति के साथ-साथ झूलने की हमारी समझ में सुधार कर सकते हैं। मैं हर एक को एक समय पर ले जाऊंगा।
विधि स्वाज़लिंग परमाणु नहीं है
मुझे अभी तक पद्धति के कार्यान्वयन को देखना है, जो समवर्ती 1 का उपयोग करने के लिए सुरक्षित है । यह वास्तव में 95% मामलों में एक समस्या नहीं है जिसे आप विधि स्विज़लिंग का उपयोग करना चाहते हैं। आमतौर पर, आप बस एक विधि के कार्यान्वयन को बदलना चाहते हैं, और आप चाहते हैं कि कार्यान्वयन आपके कार्यक्रम के पूरे जीवनकाल के लिए उपयोग किया जाए। इसका मतलब है कि आपको अपने तरीके से स्वीमिंग करनी चाहिए +(void)load
। load
वर्ग विधि अपने आवेदन के शुरू में क्रमानुसार निष्पादित किया जाता है। यदि आप यहाँ अपनी स्विज़लिंग करते हैं, तो आपके पास कोई समस्या नहीं होगी। यदि आप +(void)initialize
में झपकी लेना चाहते थे , हालांकि, आप अपने झपट्टा कार्यान्वयन में एक दौड़ की स्थिति के साथ समाप्त हो सकते हैं और रनटाइम एक अजीब स्थिति में समाप्त हो सकता है।
गैर-स्वामित्व वाले कोड का व्यवहार बदलता है
यह एक मुद्दा है, जिसमें झूलों के साथ है, लेकिन यह पूरे बिंदु की तरह है। लक्ष्य उस कोड को बदलने में सक्षम होना है। इसका कारण यह है कि लोग इसे एक बड़ी बात बताते हैं क्योंकि आप केवल एक चीज के लिए चीजों को नहीं बदल रहे हैं, जिसके लिए NSButton
आप चीजों को बदलना चाहते हैं, बल्कि NSButton
अपने आवेदन में सभी उदाहरणों के लिए। इस कारण से, जब आप झपकी लेते हैं, तो आपको सतर्क रहना चाहिए, लेकिन आपको इसे पूरी तरह से बचने की आवश्यकता नहीं है।
इसे इस तरह से सोचें ... यदि आप किसी वर्ग में एक विधि को ओवरराइड करते हैं और आप सुपर क्लास पद्धति को नहीं कहते हैं, तो आपको समस्याएँ उत्पन्न हो सकती हैं। ज्यादातर मामलों में, सुपर क्लास उम्मीद कर रहा है कि विधि को बुलाया जाएगा (जब तक कि अन्यथा दस्तावेज न किया जाए)। यदि आप इसी विचार को ज़ोर से लागू करते हैं, तो आपने अधिकांश मुद्दों को कवर कर लिया है। हमेशा मूल कार्यान्वयन को कॉल करें। यदि आप नहीं करते हैं, तो आप शायद सुरक्षित होने के लिए बहुत अधिक बदल रहे हैं।
संभव नामकरण संघर्ष
पूरे कोको में नामकरण संघर्ष एक मुद्दा है। हम अक्सर श्रेणियों में श्रेणी के नाम और विधि के नाम उपसर्ग करते हैं। दुर्भाग्य से, नामकरण संघर्ष हमारी भाषा में एक प्लेग है। हालांकि, झूलों के मामले में, वे होना जरूरी नहीं है। हमें बस उस तरीके को बदलने की ज़रूरत है, जो हम विधि के बारे में सोचते हैं कि थोड़ा सा चक्कर लगा लें। सबसे अधिक इस तरह से किया जाता है:
@interface NSView : NSObject
- (void)setFrame:(NSRect)frame;
@end
@implementation NSView (MyViewAdditions)
- (void)my_setFrame:(NSRect)frame {
// do custom work
[self my_setFrame:frame];
}
+ (void)load {
[self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}
@end
यह ठीक काम करता है, लेकिन अगर my_setFrame:
कहीं और परिभाषित किया जाता है तो क्या होगा ? यह समस्या स्वाइलिंग के लिए अद्वितीय नहीं है, लेकिन हम इसके आसपास भी काम कर सकते हैं। वर्कअराउंड में अन्य नुकसान के साथ-साथ संबोधित करने का अतिरिक्त लाभ है। यहाँ हम इसके बजाय क्या करते हैं:
@implementation NSView (MyViewAdditions)
static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);
static void MySetFrame(id self, SEL _cmd, NSRect frame) {
// do custom work
SetFrameIMP(self, _cmd, frame);
}
+ (void)load {
[self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}
@end
हालांकि यह ऑब्जेक्टिव-सी की तरह थोड़ा कम दिखता है (क्योंकि यह फ़ंक्शन पॉइंटर्स का उपयोग कर रहा है), यह किसी भी नामकरण संघर्ष से बचता है। सिद्धांत रूप में, यह मानक स्विज़लिंग के समान सटीक काम कर रहा है। यह उन लोगों के लिए थोड़ा बदलाव हो सकता है जो झूलते हुए उपयोग कर रहे हैं क्योंकि इसे थोड़ी देर के लिए परिभाषित किया गया है, लेकिन अंत में, मुझे लगता है कि यह बेहतर है। इस प्रकार स्विज़लिंग पद्धति को परिभाषित किया गया है:
typedef IMP *IMPPointer;
BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
IMP imp = NULL;
Method method = class_getInstanceMethod(class, original);
if (method) {
const char *type = method_getTypeEncoding(method);
imp = class_replaceMethod(class, original, replacement, type);
if (!imp) {
imp = method_getImplementation(method);
}
}
if (imp && store) { *store = imp; }
return (imp != NULL);
}
@implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end
विधियों का नाम बदलने से झूलने से विधि के तर्क बदल जाते हैं
यह मेरे दिमाग में एक बड़ी बात है। यही कारण है कि मानक विधि स्विज़लिंग नहीं किया जाना चाहिए। आप मूल विधि के कार्यान्वयन के लिए दिए गए तर्कों को बदल रहे हैं। यह वह जगह है जहाँ ऐसा होता है:
[self my_setFrame:frame];
यह लाइन क्या करती है:
objc_msgSend(self, @selector(my_setFrame:), frame);
जिसके क्रियान्वयन को देखने के लिए रनटाइम का उपयोग किया जाएगा my_setFrame:
। एक बार कार्यान्वयन मिल जाने के बाद, यह उन्हीं तर्कों के साथ कार्यान्वयन को लागू करता है जो दिए गए थे। इसे लागू करने का मूल कार्यान्वयन है setFrame:
, इसलिए यह आगे बढ़ता है और कॉल करता है, लेकिन _cmd
तर्क ऐसा नहीं है setFrame:
जैसा होना चाहिए। अभी है my_setFrame:
। मूल कार्यान्वयन को एक ऐसे तर्क के साथ कहा जा रहा है जिसकी यह उम्मीद नहीं थी कि यह प्राप्त होगा। यह अच्छा नहीं है।
एक सरल उपाय है - ऊपर परिभाषित वैकल्पिक स्विज़लिंग तकनीक का उपयोग करें। तर्क अपरिवर्तित रहेंगे!
स्विज़ल्स का क्रम मायने रखता है
जिस क्रम में विधियों में परिवर्तन होता है। मान लिया गया है setFrame:
कि केवल NSView
चीजों के इस क्रम की कल्पना करें:
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
क्या होता है जब विधि पर NSButton
ज़ोर दिया जाता है? अच्छी तरह से सबसे अधिक झूलना सुनिश्चित करेगा कि यह setFrame:
सभी विचारों के कार्यान्वयन की जगह नहीं ले रहा है , इसलिए यह उदाहरण विधि को ऊपर खींचेगा । यह मौजूदा कार्यान्वयन को कक्षा setFrame:
में फिर से परिभाषित करने के लिए उपयोग करेगा NSButton
ताकि कार्यान्वयन का आदान-प्रदान सभी विचारों को प्रभावित न करें। मौजूदा कार्यान्वयन वह है जिसे परिभाषित किया गया है NSView
। एक ही बात तब होगी जब NSControl
(फिर से NSView
कार्यान्वयन का उपयोग करते हुए ) झूलना होगा ।
जब आप setFrame:
एक बटन पर कॉल करते हैं, तो यह आपके स्विज्ड विधि को कॉल करेगा, और फिर सीधे उस setFrame:
विधि पर कूद जाएगा जिसे मूल रूप से परिभाषित किया गया है NSView
। NSControl
और NSView
swizzled कार्यान्वयन बुलाया नहीं किया जाएगा।
लेकिन क्या होगा अगर आदेश थे:
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
चूँकि दृश्य स्वीज़िंग पहले होता है, नियंत्रण स्विज़लिंग सही विधि को खींचने में सक्षम होगा । इसी तरह, चूंकि बटन स्वाइज़ करने से पहले कंट्रोल स्विज़लिंग होता था, इसलिए बटन कंट्रोल के स्विज़ल्ड कार्यान्वयन को खींच लेगा setFrame:
। यह थोड़ा भ्रमित करने वाला है, लेकिन यह सही क्रम है। हम चीजों के इस क्रम को कैसे सुनिश्चित कर सकते हैं?
फिर, बस load
चीजों को स्वाइप करने के लिए उपयोग करें। यदि आप में झपकी लेते हैं load
और आप केवल लोड की जा रही कक्षा में परिवर्तन करते हैं, तो आप सुरक्षित रहेंगे। load
विधि गारंटी देता है कि सुपर वर्ग लोड विधि किसी भी उपवर्गों से पहले बुलाया जाएगा। हम सही सही आदेश मिलेगा!
समझने में मुश्किल (पुनरावर्ती दिखता है)
एक पारंपरिक रूप से परिभाषित जलपोत विधि को देखते हुए, मुझे लगता है कि वास्तव में यह बताना मुश्किल है कि क्या हो रहा है। लेकिन हमने जिस वैकल्पिक तरीके से ऊपर झूलते हुए देखा है, उसे देखना बहुत आसान है। यह पहले से ही हल हो गया है!
डिबग करना मुश्किल है
डिबगिंग के दौरान भ्रम की स्थिति में से एक में एक अजीब backtrace दिखाई दे रहा है जहां घुले हुए नामों को मिलाया जाता है और सब कुछ आपके सिर में उछल जाता है। फिर से, वैकल्पिक कार्यान्वयन इसे संबोधित करता है। आपको बैकट्रैक में स्पष्ट रूप से नामित कार्य दिखाई देंगे। फिर भी, झूलना डिबग करना मुश्किल हो सकता है क्योंकि यह याद रखना कठिन है कि झूलने का क्या प्रभाव पड़ रहा है। अपने कोड को अच्छी तरह से प्रलेखित करें (भले ही आपको लगता है कि आप केवल वही हैं जो इसे कभी देख पाएंगे)। अच्छी प्रथाओं का पालन करें, और आप ठीक हो जाएंगे। मल्टी-थ्रेडेड कोड की तुलना में डिबग करना कठिन नहीं है।
निष्कर्ष
यदि सही तरीके से उपयोग किया जाए तो विधि स्वाज़लिंग सुरक्षित है। एक सरल सुरक्षा उपाय जो आप अपना सकते हैं, वह केवल स्विज़ल में है load
। प्रोग्रामिंग में कई चीजों की तरह, यह खतरनाक हो सकता है, लेकिन परिणामों को समझने से आप इसे ठीक से उपयोग कर पाएंगे।
1 ऊपर बताई गई स्विज़लिंग विधि का उपयोग करके, आप ट्रैम्पोलिन का उपयोग करने के लिए चीजों को सुरक्षित रख सकते हैं। आपको दो trampolines की आवश्यकता होगी। विधि के प्रारंभ में, आपको फ़ंक्शन पॉइंटर store
को एक फ़ंक्शन को असाइन करना होगा, जो उस पते तक घूमता है, जिसमें पता store
बदलने के लिए इंगित किया गया है। यह किसी भी दौड़ की स्थिति से बचना होगा, जिसमें store
फंक्शन पॉइंटर सेट करने में सक्षम होने से पहले स्विज़ल्ड पद्धति को बुलाया गया था । फिर आपको उस मामले में एक trampoline का उपयोग करने की आवश्यकता होगी जहां कार्यान्वयन पहले से ही वर्ग में परिभाषित नहीं किया गया है और जिसमें trampoline लुकअप है और सुपर क्लास विधि को ठीक से कॉल करें। विधि को परिभाषित करते हुए इसलिए यह गतिशील रूप से दिखता है कि सुपर कार्यान्वयन यह सुनिश्चित करेगा कि स्विज़लिंग कॉल का क्रम मायने नहीं रखता है।