दमन की चेतावनी "श्रेणी एक ऐसी विधि लागू कर रही है जिसे उसके प्राथमिक वर्ग द्वारा भी लागू किया जाएगा"


98

मैं सोच रहा था कि चेतावनी को कैसे दबाया जाए:

श्रेणी एक ऐसी विधि लागू कर रही है जिसे इसके प्राथमिक वर्ग द्वारा भी लागू किया जाएगा।

मेरे पास एक विशिष्ट कोड श्रेणी के लिए यह है:

+ (UIFont *)systemFontOfSize:(CGFloat)fontSize {
    return [self aCustomFontOfSize:fontSize];
}

विधि स्वाइलिंग द्वारा। हालांकि मैं ऐसा नहीं करता - शायद आप एक UIFont उपवर्ग बना सकते हैं जो इसके बजाय एक ही विधि को ओवरराइड करता है, और superअन्यथा कॉल करें ।
एलन ज़ीनो

4
आपकी समस्या चेतावनी नहीं है। आपकी समस्या यह है कि आपके पास वही विधि नाम है, जो समस्याओं को जन्म देने वाला है।
gnasher729 16

ऑब्जेक्ट-सी में श्रेणियों का उपयोग करके ओवरराइडिंग विधियों को देखें कारणों में आपको श्रेणियों का उपयोग करके तरीकों को ओवरराइडिंग नहीं करना चाहिए, और वैकल्पिक समाधानों के लिए।
सेंसफुल

यदि आप लोग एप्लिकेशन-वाइड फ़ॉन्ट को सेट करने के लिए अधिक सुरुचिपूर्ण समाधान जानते हैं, तो मैं वास्तव में इसे सुनना चाहूंगा!
1

जवाबों:


64

एक श्रेणी आपको मौजूदा कक्षा में नए तरीके जोड़ने की अनुमति देती है। यदि आप एक विधि को फिर से लागू करना चाहते हैं जो पहले से ही कक्षा में मौजूद है, तो आप आमतौर पर एक श्रेणी के बजाय एक उपवर्ग बनाते हैं।

Apple प्रलेखन: मौजूदा कक्षाओं को अनुकूलित करना

यदि किसी श्रेणी में घोषित विधि का नाम मूल वर्ग में एक विधि के समान है, या उसी वर्ग (या यहां तक ​​कि एक सुपरक्लास) पर किसी अन्य श्रेणी में एक विधि है, तो व्यवहार अपरिभाषित है कि किस विधि कार्यान्वयन का उपयोग किया जाता है क्रम।

एक ही कक्षा में एक ही हस्ताक्षर के साथ दो तरीके अप्रत्याशित व्यवहार को जन्म देंगे, क्योंकि प्रत्येक कॉलर यह निर्दिष्ट नहीं कर सकता है कि उन्हें कौन सा कार्यान्वयन चाहिए।

तो, आपको या तो एक श्रेणी का उपयोग करना चाहिए और विधि के नाम प्रदान करने चाहिए जो वर्ग के लिए नए और अनूठे हों, या किसी वर्ग में मौजूदा पद्धति के व्यवहार को बदलना चाहते हों।


1
मैं उन विचारों से पूरी तरह सहमत हूं जो ऊपर बताए गए हैं और विकास के दौरान उनका पालन करने की कोशिश कर रहे हैं। लेकिन फिर भी ऐसे मामले हो सकते हैं, जहां श्रेणी में ओवरराइड विधियां उपयुक्त हो सकती हैं। उदाहरण के लिए, ऐसे मामले जहां कई वंशानुक्रम (जैसे सी ++) या इंटरफेस (जैसे सी #) का उपयोग किया जा सकता है। मेरे प्रोजेक्ट में बस इसका सामना करना पड़ा और महसूस किया कि श्रेणियों में ओवरराइडिंग तरीके सबसे अच्छे विकल्प हैं।
Peetonn

4
यह उपयोगी हो सकता है जब यूनिट कुछ कोड का परीक्षण कर रही है जिसमें एक सिंगलटन है। आदर्श रूप से, सिंगलेट्स को एक प्रोटोकॉल के रूप में कोड में इंजेक्ट किया जाना चाहिए, जिससे आप कार्यान्वयन को बंद कर सकते हैं। लेकिन यदि आपके पास पहले से ही आपके कोड के अंदर एक एम्बेडेड है, तो आप अपने यूनिट टेस्ट में सिंगलटन की एक श्रेणी जोड़ सकते हैं और साझाकरण और उन तरीकों को पार कर सकते हैं जो आपको डमी ऑब्जेक्ट में बदलने के लिए नियंत्रित करते हैं।
बंदगीजपाइसा

धन्यवाद @PychoDad मैंने लिंक को अपडेट किया और प्रलेखन से एक उद्धरण जोड़ा जो इस पद के लिए प्रासंगिक है।
bneely

अछा लगता है। क्या Apple मौजूदा पद्धति नाम के साथ श्रेणी का उपयोग करने के व्यवहार पर प्रलेखन प्रदान करता है?
जजक्ट्रा

1
अगर मुझे श्रेणी या उपवर्ग के साथ जाना चाहिए तो कमाल नहीं था, :-)
kernix

343

हालाँकि जो कुछ भी कहा गया है वह सही है, यह वास्तव में आपके सवाल का जवाब नहीं देता है कि चेतावनी को कैसे दबाया जाए।

यदि आपको किसी कारण से यह कोड रखना है (मेरे मामले में मेरे पास मेरे प्रोजेक्ट में हॉकीकीट है और वे UIImage श्रेणी में एक विधि को ओवरराइड करते हैं [संपादित करें: यह अब मामला नहीं है)) और आपको अपनी परियोजना को संकलित करने की आवश्यकता है , आप #pragmaइस तरह की चेतावनी को ब्लॉक करने के लिए बयानों का उपयोग कर सकते हैं :

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"

// do your override

#pragma clang diagnostic pop

मुझे यहाँ जानकारी मिली: http://www.cocoabuilder.com/archive/xcode/313767-disable-warning-for-override-in-category.html


आपका बहुत बहुत धन्यवाद! इसलिए, मैं देख रहा हूँ कि प्रज्ञाएँ चेतावनी को भी दबा सकती हैं। :
कांस्टेंटिनो त्सरोहास

हां, और हालांकि ये एलएलवीएम विशिष्ट कथन हैं, जीसीसी के लिए भी समान हैं।
बेन बैरन

1
आपके परीक्षण प्रोजेक्ट में चेतावनी एक लिंकर चेतावनी है, एक llvm संकलक चेतावनी नहीं है, इसलिए llvm pragma कुछ भी नहीं कर रहा है। हालाँकि, आप देखेंगे कि आपका परीक्षण प्रोजेक्ट अभी भी "व्यवहार चेतावनी के रूप में त्रुटियों के साथ" बनाता है क्योंकि यह एक लिंकर चेतावनी है।
बेन बैरन

12
यह वास्तव में स्वीकृत उत्तर होना चाहिए, यह देखते हुए कि यह वास्तव में प्रश्न का उत्तर देता है।
रोब जोंस

1
यह उत्तर सही होना चाहिए। वैसे भी इसके पास उत्तर के रूप में चयनित एक से अधिक वोट हैं।
जुआन कैटलान

20

एक बेहतर विकल्प (देखें कि इस चेतावनी आपको आपदा से क्यों बचा रही है) का जवाब है, पद्धति का उपयोग करना है। विधि स्विज़लिंग का उपयोग करके, आप "जीत" की अनिश्चितता के बिना किसी श्रेणी से मौजूदा पद्धति को बदल सकते हैं, और पुरानी पद्धति से कॉल करने की क्षमता को संरक्षित करते हुए। रहस्य ओवरराइड को एक अलग विधि का नाम देना है, फिर रनटाइम फ़ंक्शन का उपयोग करके उन्हें स्वैप करें।

#import <objc/runtime.h> 
#import <objc/message.h>

void MethodSwizzle(Class c, SEL orig, SEL new) {
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);
    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
    method_exchangeImplementations(origMethod, newMethod);
}

फिर अपने कस्टम कार्यान्वयन को परिभाषित करें:

+ (UIFont *)mySystemFontOfSize:(CGFloat)fontSize {
...
}

आपके साथ डिफ़ॉल्ट कार्यान्वयन को ओवरराइड करें:

MethodSwizzle([UIFont class], @selector(systemFontOfSize:), @selector(mySystemFontOfSize:));

10

इसे अपने कोड में आज़माएं:

+(void)load{
    EXCHANGE_METHOD(Method1, Method1Impl);
}

UPDATE2: इस मैक्रो को जोड़ें

#import <Foundation/Foundation.h>
#define EXCHANGE_METHOD(a,b) [[self class]exchangeMethod:@selector(a) withNewMethod:@selector(b)]

@interface NSObject (MethodExchange)
+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel;
@end

#import <objc/runtime.h>

@implementation NSObject (MethodExchange)

+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel{
    Class class = [self class];

    Method origMethod = class_getInstanceMethod(class, origSel);
    if (!origMethod){
        origMethod = class_getClassMethod(class, origSel);
    }
    if (!origMethod)
        @throw [NSException exceptionWithName:@"Original method not found" reason:nil userInfo:nil];
    Method newMethod = class_getInstanceMethod(class, newSel);
    if (!newMethod){
        newMethod = class_getClassMethod(class, newSel);
    }
    if (!newMethod)
        @throw [NSException exceptionWithName:@"New method not found" reason:nil userInfo:nil];
    if (origMethod==newMethod)
        @throw [NSException exceptionWithName:@"Methods are the same" reason:nil userInfo:nil];
    method_exchangeImplementations(origMethod, newMethod);
}

@end

1
यह एक पूर्ण उदाहरण नहीं है। EXCHANGE_METHOD नाम का कोई मैक्रो वास्तव में ऑब्जेक्टिव-सी रनटाइम द्वारा परिभाषित नहीं किया गया है।
रिचर्ड जे। रॉस III

@ वैताल स्टिल -1। वह विधि वर्ग प्रकार के लिए लागू नहीं की जाती है। आप किस फ्रेमवर्क का उपयोग कर रहे हैं?
रिचर्ड जे। रॉस III

क्षमा करें, इसे आज़माएँ मैंने बनाई फ़ाइल NSObject + MethodExchange
विटालि गेरवाज़ुक

NSObject पर श्रेणी के साथ भी क्यों मैक्रो के साथ परेशान? सिर्फ 'एक्सचेंजमेथोड' के साथ ही क्यों नहीं झूला?
हैवानब्रग

5

आप इस संकलक चेतावनी को दबाने के लिए विधि स्विज़लिंग का उपयोग कर सकते हैं। जब हम UITextBorderStyleNone के साथ एक कस्टम बैकग्राउंड का उपयोग करते हैं, तो एक UITextField में हाशिये के लिए मैंने यह तरीका लागू किया है।

#import <UIKit/UIKit.h>

@interface UITextField (UITextFieldCatagory)

+(void)load;
- (CGRect)textRectForBoundsCustom:(CGRect)bounds;
- (CGRect)editingRectForBoundsCustom:(CGRect)bounds;
@end

#import "UITextField+UITextFieldCatagory.h"
#import <objc/objc-runtime.h>

@implementation UITextField (UITextFieldCatagory)

+(void)load
{
    Method textRectForBounds = class_getInstanceMethod(self, @selector(textRectForBounds:));
    Method textRectForBoundsCustom = class_getInstanceMethod(self, @selector(textRectForBoundsCustom:));

    Method editingRectForBounds = class_getInstanceMethod(self, @selector(editingRectForBounds:));
    Method editingRectForBoundsCustom = class_getInstanceMethod(self, @selector(editingRectForBoundsCustom:));


    method_exchangeImplementations(textRectForBounds, textRectForBoundsCustom);
    method_exchangeImplementations(editingRectForBounds, editingRectForBoundsCustom);

}


- (CGRect)textRectForBoundsCustom:(CGRect)bounds
{
    CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
    return inset;
}

- (CGRect)editingRectForBoundsCustom:(CGRect)bounds
{
    CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
    return inset;
}

@end

2

ओवर-राइडिंग गुण एक क्लास एक्सटेंशन (बेनामी श्रेणी) के लिए मान्य है, लेकिन एक नियमित श्रेणी के लिए नहीं।

क्लास एक्सटेंशन (बेनामी श्रेणी) का उपयोग करने वाले ऐप्पल डॉक्स के अनुसार, आप एक सार्वजनिक वर्ग के लिए एक निजी इंटरफ़ेस बना सकते हैं, जैसे कि निजी इंटरफ़ेस सार्वजनिक रूप से उजागर गुणों को ओवरराइड कर सकता है। यानी आप प्रॉपर्टी को आसानी से रीडायराइट से बदल सकते हैं।

इसके लिए एक उपयोग मामला तब होता है जब आप पुस्तकालयों को लिखते हैं जो सार्वजनिक संपत्तियों तक पहुंच को प्रतिबंधित करते हैं, जबकि उसी संपत्ति को पुस्तकालय के भीतर पूर्ण पढ़ने लिखने की आवश्यकता होती है।

Apple डॉक्स लिंक: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExisting.asses.html

" निजी जानकारी छिपाने के लिए क्लास एक्सटेंशन्स का उपयोग करें " खोजें ।

तो यह तकनीक एक क्लास एक्सटेंशन के लिए मान्य है, लेकिन किसी श्रेणी के लिए नहीं।


1

श्रेणियां अच्छी बात हैं, लेकिन उनका दुरुपयोग किया जा सकता है। जब श्रेणियों को लिखना एक सिद्धांत के रूप में बाहर निकलने के तरीकों को फिर से लागू नहीं करना चाहिए। ऐसा करने से अजीब दुष्प्रभाव हो सकता है क्योंकि अब आप कोड को फिर से लिख रहे हैं कि कोई अन्य वर्ग निर्भर करता है। आप एक ज्ञात वर्ग को तोड़ सकते हैं, और अपने डिबगर को अंदर-बाहर मोड़ सकते हैं। यह केवल खराब प्रोग्रामिंग है।

यदि आपको इसकी आवश्यकता है, तो आपको वास्तव में इसे उप-वर्ग करना चाहिए।

फिर स्वाइलिंग का सुझाव, जो मेरे लिए बहुत बड़ा NO-NO-NO है।

इसे रनवे पर स्वाइप करना पूर्ण NO-NO-NO है।

आप एक नारंगी जैसा दिखना चाहते हैं, लेकिन केवल रनटाइम पर? यदि आप एक नारंगी चाहते हैं, तो एक नारंगी लिखें।

डॉन एक केले के रूप में नहीं दिखते और नारंगी की तरह काम करते हैं। और इससे भी बदतर: अपने केले को एक गुप्त एजेंट में मत बदलो जो संतरे के समर्थन में चुपचाप केले को दुनिया भर में तोड़फोड़ करेगा।

ओह!


3
परीक्षण के माहौल में मज़ाक करने वाले व्यवहार के लिए रनटाइम पर शपथ लेना उपयोगी हो सकता है।
बेन जी

2
हालांकि विनोदी, आपका जवाब वास्तव में यह कहने से अधिक नहीं है कि सभी संभव तरीके खराब हैं। जानवर की प्रकृति यह है कि कभी-कभी आप वास्तव में उप-वर्ग नहीं कर सकते हैं, इसलिए आपको एक श्रेणी के साथ छोड़ दिया जाता है और खासकर यदि आप उस वर्ग के लिए कोड नहीं रखते हैं जिसे आप वर्गीकृत कर रहे हैं, तो कभी-कभी आपको स्विज़ल करने की आवश्यकता होती है, और यह अवांछनीय विधि प्रासंगिक नहीं है।
हैवानब्रग

1

मुझे यह समस्या तब हुई जब मैंने एक श्रेणी में एक प्रतिनिधि विधि लागू की, बल्कि मुख्य वर्ग (भले ही कोई मुख्य वर्ग कार्यान्वयन नहीं था)। मेरे लिए समाधान मुख्य वर्ग हेडर फ़ाइल से श्रेणी हेडर फ़ाइल में स्थानांतरित करना था। यह ठीक काम करता है

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.