क्या NSArray, NSMutableArray, आदि पर टाइपिंग को लागू करने का कोई तरीका है?


जवाबों:


35

आप -addSomeClass:कंपाइल-टाइम स्टैटिक टाइप चेकिंग की अनुमति देने के लिए एक विधि के साथ एक श्रेणी बना सकते हैं (इसलिए कंपाइलर आपको बता सकता है कि क्या आप उस वस्तु को जोड़ने की कोशिश करते हैं जो यह जानता है कि उस विधि के माध्यम से एक अलग वर्ग है), लेकिन इसे लागू करने का कोई वास्तविक तरीका नहीं है एक सरणी में केवल किसी दिए गए वर्ग की वस्तुएँ होती हैं।

सामान्य तौर पर, उद्देश्य-सी में इस तरह की बाधा की आवश्यकता नहीं लगती है। मुझे नहीं लगता कि मैंने कभी उस सुविधा के लिए एक अनुभवी कोको प्रोग्रामर को सुना है। केवल वे लोग जो अन्य भाषाओं के प्रोग्रामर लगते हैं जो अभी भी उन भाषाओं में सोच रहे हैं। यदि आप किसी दिए गए वर्ग की वस्तुओं को किसी सरणी में चाहते हैं, तो केवल उस वर्ग की वस्तुओं को वहाँ चिपकाएँ। यदि आप यह परीक्षण करना चाहते हैं कि आपका कोड ठीक से व्यवहार कर रहा है, तो उसका परीक्षण करें।


136
मुझे लगता है कि 'अनुभवी कोको प्रोग्रामर' को यह नहीं पता है कि वे क्या याद कर रहे हैं - जावा के साथ अनुभव से पता चलता है कि टाइप वैरिएबल कोड की समझ में सुधार करते हैं और अधिक रिफैक्टरिंग संभव बनाते हैं।
tgdavies

11
खैर, जावा का जेनरिक सपोर्ट इसमें अपने आप ही टूट गया है, क्योंकि उन्होंने इसे शुरू से ही नहीं रखा है ...
dertoni

28
गोटी @tgdavies से सहमत हैं। मुझे सी # के साथ होने वाली इंटैलिजेंस और रीफैक्टरिंग क्षमताओं की याद आती है। जब मुझे डायनेमिक टाइपिंग चाहिए तो मैं इसे C # 4.0 में प्राप्त कर सकता हूं। जब मैं जोरदार प्रकार के सामान चाहता हूं तो मेरे पास वह भी हो सकता है। मैंने पाया है कि उन दोनों चीजों के लिए एक समय और स्थान है।
स्टीव

18
@ Crakrit क्या उद्देश्य-सी के बारे में है जो इसे 'आवश्यक नहीं' बनाता है? क्या आपको लगा कि जब आप C # का उपयोग कर रहे थे तो यह आवश्यक था? मैंने बहुत से लोगों को यह कहते हुए सुना है कि आपको ऑब्जेक्टिव-सी में इसकी आवश्यकता नहीं है, लेकिन मुझे लगता है कि इन लोगों को लगता है कि आपको किसी भी भाषा में इसकी आवश्यकता नहीं है, जो इसे वरीयता / शैली का मुद्दा बनाता है, आवश्यकता का नहीं।
बकार

17
क्या यह आपके कंपाइलर को वास्तव में समस्याओं को खोजने में मदद करने की अनुमति देने के बारे में नहीं है। निश्चित रूप से आप कह सकते हैं "यदि आप किसी दिए गए वर्ग की वस्तुओं को किसी सरणी में चाहते हैं, तो केवल उस कक्षा की वस्तुओं को वहाँ चिपकाएँ।" लेकिन अगर परीक्षणों को लागू करने का एकमात्र तरीका है, तो आप एक नुकसान में हैं। कोड लिखने से जितना दूर आप एक समस्या पाते हैं, उतना ही महंगा यह समस्या है।
ग्रीनकिवी

145

किसी ने इसे अभी तक यहाँ नहीं रखा है, इसलिए मैं इसे करूँगा!

Tthis अब आधिकारिक तौर पर ऑब्जेक्टिव-सी में समर्थित है। Xcode 7 के अनुसार, आप निम्न सिंटैक्स का उपयोग कर सकते हैं:

NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];

ध्यान दें

यह ध्यान रखना महत्वपूर्ण है कि ये केवल संकलक चेतावनी हैं और आप तकनीकी रूप से किसी भी वस्तु को अपने सरणी में सम्मिलित कर सकते हैं। ऐसी लिपियाँ उपलब्ध हैं जो सभी चेतावनियों को त्रुटियों में बदल देती हैं जो निर्माण को रोकती हैं।


मैं यहाँ आलसी हो रहा हूँ, लेकिन यह केवल XCode 7 में ही क्यों उपलब्ध है? हम nonnullXCode 6 में उपयोग कर सकते हैं और जहां तक ​​मुझे याद है, वे एक ही समय में पेश किए गए थे। इसके अलावा, क्या इस तरह की अवधारणाओं का उपयोग XCode संस्करण या iOS संस्करण पर निर्भर करता है?
ग्वेन

@Guven - nullability 6 में आई, आप सही हैं, लेकिन ObjC जेनरिक को Xcode 7. तक पेश नहीं किया गया
लोगान

मुझे पूरा यकीन है कि यह केवल Xcode संस्करण पर निर्भर करता है। जेनरिक केवल संकलक चेतावनी हैं और रनटाइम पर संकेत नहीं दिए गए हैं। मुझे पूरा यकीन है कि आप जो भी ओस चाहते हैं उसे आप संकलित कर सकते हैं।
लोगन

2
@DeanKelly - आप ऐसा कर सकते हैं: @property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; थोड़ा भद्दा लगता है, लेकिन चाल है!
लोगन

1
@ लोगन, केवल लिपियों का सेट नहीं है, जो किसी भी चेतावनी का पता लगाने के मामले में निर्माण को रोकता है। Xcode में "कॉन्फ़िगरेशन" नाम का एक सही तंत्र है। इसे देखें boredzo.org/blog/archives/2009-11-07/warnings
adnako

53

यह दृढ़ता से टाइप की भाषाओं (जैसे C ++ या जावा) से संक्रमण करने वाले लोगों के लिए एक अपेक्षाकृत सामान्य प्रश्न है, जो पायथन, रूबी, या ऑब्जेक्टिव-सी जैसी अधिक कमजोर या गतिशील रूप से टाइप की गई भाषाओं में है। ऑब्जेक्टिव-सी में, अधिकांश ऑब्जेक्ट्स NSObject(प्रकार id) से विरासत में प्राप्त होते हैं ( बाकी अन्य मूल वर्ग से विरासत में मिलते हैं जैसे कि NSProxyऔर टाइप भी हो सकते हैं id), और किसी भी वस्तु को कोई भी संदेश भेजा जा सकता है। बेशक, एक उदाहरण के लिए एक संदेश भेजना कि यह नहीं पहचानता है एक रनटाइम त्रुटि का कारण हो सकता है (और एक संकलक चेतावनी का कारण भी होगा उपयुक्त-डब्ल्यू झंडे के साथ किया )। जब तक एक उदाहरण आपके द्वारा भेजे गए संदेश का जवाब देता है, तब तक आप परवाह नहीं कर सकते हैं कि वह किस वर्ग का है। इसे अक्सर "बतख टाइपिंग" के रूप में संदर्भित किया जाता है क्योंकि "अगर यह एक बतख की तरह बन्द हो जाता है [अर्थात एक चयनकर्ता को जवाब देता है], यह एक बतख है [अर्थात यह संदेश को संभाल सकता है; जो परवाह करता है कि यह किस वर्ग का है]"।

आप परीक्षण कर सकते हैं कि क्या कोई उदाहरण -(BOOL)respondsToSelector:(SEL)selectorविधि के साथ रन टाइम पर चयनकर्ता को जवाब देता है । मान लें कि आप एक सरणी में हर मामले पर एक विधि कॉल करना चाहते हैं, लेकिन यकीन नहीं है कि सभी उदाहरणों संदेश संभाल कर सकते हैं (ताकि आप सिर्फ उपयोग नहीं कर सकते हैं NSArrayकी -[NSArray makeObjectsPerformSelector:], कुछ इस तरह काम करेगा:

for(id o in myArray) {
  if([o respondsToSelector:@selector(myMethod)]) {
    [o myMethod];
  }
}

यदि आप उन इंस्टेंस के लिए स्रोत कोड को नियंत्रित करते हैं जो उस पद्धति को लागू करते हैं जिसे आप कॉल करना चाहते हैं, तो अधिक सामान्य दृष्टिकोण यह होगा @protocolकि उन तरीकों को परिभाषित किया जाए और घोषित किया जाए कि प्रश्न में कक्षाएं उनके प्रोटोकॉल में घोषणा को लागू करती हैं। इस उपयोग में, एक @protocolजावा इंटरफेस या एक सी + + अमूर्त आधार वर्ग के अनुरूप है। फिर आप प्रत्येक विधि की प्रतिक्रिया के बजाय पूरे प्रोटोकॉल के अनुरूप परीक्षण कर सकते हैं। पिछले उदाहरण में, इससे बहुत फर्क नहीं पड़ेगा, लेकिन अगर आप कई तरीके बता रहे हैं, तो यह चीजों को सरल बना सकता है। उदाहरण तब होगा:

for(id o in myArray) {
  if([o conformsToProtocol:@protocol(MyProtocol)]) {
    [o myMethod];
  }
}

यह मानते हुए कि MyProtocolवाणी myMethod। यह दूसरा दृष्टिकोण इष्ट है क्योंकि यह पहले से अधिक कोड के इरादे को स्पष्ट करता है।

अक्सर, इन तरीकों में से एक आपको परवाह करने से मुक्त करता है कि क्या एक सरणी में सभी ऑब्जेक्ट एक दिए गए प्रकार के हैं। यदि आप अभी भी देखभाल करते हैं, तो मानक गतिशील भाषा दृष्टिकोण इकाई परीक्षण, इकाई परीक्षण, इकाई परीक्षण है। क्योंकि इस आवश्यकता में एक प्रतिगमन एक (संभावित अप्राप्य) रनटाइम (संकलित समय नहीं) त्रुटि उत्पन्न करेगा, आपको व्यवहार को सत्यापित करने के लिए परीक्षण कवरेज की आवश्यकता होगी ताकि आप किसी क्रैश को जंगली में जारी न करें। इस स्थिति में, एक ऑपरेशन को सुधारें जो सरणी को संशोधित करता है, फिर सत्यापित करें कि सरणी के सभी उदाहरण किसी दिए गए वर्ग के हैं। उचित परीक्षण कवरेज के साथ, आपको उदाहरण पहचान को सत्यापित करने के अतिरिक्त रनटाइम ओवरहेड की भी आवश्यकता नहीं है। आपके पास अच्छी इकाई परीक्षण कवरेज है, है न?


35
यूनिट परीक्षण एक सभ्य प्रकार की प्रणाली का विकल्प नहीं है।
tba

8
हाँ, टाइपिंग सरणियों की जरूरत है जो टूलींग की आवश्यकता होगी। मुझे यकीन है कि @BarryWark (और कोई भी व्यक्ति जिसने किसी भी कोडबेस को छुआ है जिसे उसे उपयोग करने, पढ़ने, समझने और समर्थन करने की आवश्यकता है) में 100% कोड कवरेज है। हालांकि मैं शर्त लगाता हूं कि आप कच्चे idएस का उपयोग नहीं करते हैं , जहां आवश्यक हो, सिवाय इसके कि जावा कोडर्स Objectएस के आसपास से गुजरते हैं । क्यों नहीं? यदि आपको यूनिट टेस्ट मिल गए हैं तो इसकी आवश्यकता नहीं है? क्योंकि यह वहाँ है और टाइप किए गए सरणियों के रूप में आपके कोड को अधिक बनाए रखने योग्य बनाता है। ऐसा लगता है कि मंच में निवेश किए गए लोग एक बिंदु को स्वीकार नहीं करना चाहते हैं, और इसलिए कारणों का आविष्कार करना कि यह चूक वास्तव में एक लाभ है।
फंकीब्रिज

"बतख टाइपिंग" ?? यह प्रफुल्लित करने वाला है! उसके बारे में पहले कभी नहीं सुना।
15:40

11

आप NSMutableArrayप्रकार की सुरक्षा को लागू करने के लिए उपवर्ग बना सकते हैं ।

NSMutableArrayएक वर्ग क्लस्टर है , इसलिए उपवर्ग तुच्छ नहीं है। मैंने NSArrayउस वर्ग के अंदर एक सरणी से इनहेरिट करना और इनवॉइस अग्रेषित करना समाप्त कर दिया । परिणाम एक वर्ग कहा जाता है ConcreteMutableArrayजो है उपवर्ग करने के लिए आसान। यहाँ मैं क्या लेकर आया हूँ:

अद्यतन: एक वर्ग क्लस्टर उपवर्ग पर माइक ऐश से इस ब्लॉग पोस्ट की जाँच करें

अपनी परियोजना में उन फ़ाइलों को शामिल करें, फिर मैक्रो का उपयोग करके अपनी इच्छा के अनुसार किसी भी प्रकार का उत्पादन करें:

MyArrayTypes.h

CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)

MyArrayTypes.m

CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)

उपयोग:

NSStringArray* strings = [NSStringArray array];
[strings add:@"Hello"];
NSString* str = [strings get:0];

[strings add:[User new]];  //compiler error
User* user = [strings get:0];  //compiler error

अन्य विचार

  • इससे विरासत में मिली NSArray क्रमबद्धता / deserialization का समर्थन करने के लिए
  • अपने स्वाद के आधार पर, आप जेनेरिक विधियों को ओवरराइड / छुपाना चाह सकते हैं

    - (void) addObject:(id)anObject


अच्छा है, लेकिन अभी इसके लिए कुछ तरीकों को पछाड़कर मजबूत टाइपिंग की कमी है। वर्तमान में यह केवल कमजोर टाइपिंग है।
क्यूर

7

ऑब्जेक्टिव-सी के लिए https://github.com/tomersh/Objective-C-Generics , एक संकलन-समय (प्रीप्रोसेसर-कार्यान्वित) जेनरिक कार्यान्वयन पर एक नज़र है। इस ब्लॉग पोस्ट में एक अच्छा अवलोकन है। मूल रूप से आपको संकलन-समय की जाँच (चेतावनी या त्रुटियाँ) मिलती हैं, लेकिन जेनरिक के लिए कोई रनटाइम जुर्माना नहीं।


1
मैं इसे बाहर की कोशिश की, बहुत अच्छा विचार है, लेकिन दुख की बात है और यह जोड़ा तत्वों की जाँच नहीं करता है।
द्विवार्षिक

4

यह गीथब प्रोजेक्ट वास्तव में उस कार्यक्षमता को लागू करता है।

आप तब उपयोग कर सकते हैं <> कोष्ठक का हैं, जैसे आप C # में करेंगे।

उनके उदाहरणों से:

NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed

0

एक संभावित तरीका NSArray को उपवर्गित कर सकता है लेकिन Apple इसे न करने की सलाह देता है। टाइप किए गए NSArray की वास्तविक आवश्यकता के बारे में दो बार सोचना आसान है।


1
यह संकलन समय पर स्थिर प्रकार की जाँच करने के लिए समय बचाता है, संपादन और भी बेहतर है। विशेष रूप से तब मददगार होती है जब आप दीर्घकालिक उपयोग के लिए काम लिख रहे हों।
.१३

0

मैंने एक NSArray उपवर्ग बनाया, जो NSArray के वर्ग-क्लस्टर प्रकृति के साथ समस्याओं से बचने के लिए ivar के रूप में एक NSArray ऑब्जेक्ट का उपयोग कर रहा है। किसी ऑब्जेक्ट को जोड़ने या अस्वीकार करने में ब्लॉक लगते हैं।

केवल NSString ऑब्जेक्ट को अनुमति देने के लिए, आप एक के AddBlockरूप में परिभाषित कर सकते हैं

^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
}

आप यह निर्धारित करने के FailBlockलिए क्या कर सकते हैं कि क्या करना है, अगर कोई तत्व परीक्षण में विफल हो गया - फ़िल्टर करने के लिए इनायत से विफल हो, इसे किसी अन्य सरणी में जोड़ें, या - यह डिफ़ॉल्ट है - एक अपवाद बढ़ाएं।

VSBlockTestedObjectArray.h

#import <Foundation/Foundation.h>
typedef BOOL(^AddBlock)(id element); 
typedef void(^FailBlock)(id element); 

@interface VSBlockTestedObjectArray : NSMutableArray

@property (nonatomic, copy, readonly) AddBlock testBlock;
@property (nonatomic, copy, readonly) FailBlock failBlock;

-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock Capacity:(NSUInteger)capacity;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock;    
@end

VSBlockTestedObjectArray.m

#import "VSBlockTestedObjectArray.h"

@interface VSBlockTestedObjectArray ()
@property (nonatomic, retain) NSMutableArray *realArray;
-(void)errorWhileInitializing:(SEL)selector;
@end

@implementation VSBlockTestedObjectArray
@synthesize testBlock = _testBlock;
@synthesize failBlock = _failBlock;
@synthesize realArray = _realArray;


-(id)initWithCapacity:(NSUInteger)capacity
{
    if (self = [super init]) {
        _realArray = [[NSMutableArray alloc] initWithCapacity:capacity];
    }

    return self;
}

-(id)initWithTestBlock:(AddBlock)testBlock 
             FailBlock:(FailBlock)failBlock 
              Capacity:(NSUInteger)capacity
{
    self = [self initWithCapacity:capacity];
    if (self) {
        _testBlock = [testBlock copy];
        _failBlock = [failBlock copy];
    }

    return self;
}

-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock
{
    return [self initWithTestBlock:testBlock FailBlock:failBlock Capacity:0];
}

-(id)initWithTestBlock:(AddBlock)testBlock
{
    return [self initWithTestBlock:testBlock FailBlock:^(id element) {
        [NSException raise:@"NotSupportedElement" format:@"%@ faild the test and can't be add to this VSBlockTestedObjectArray", element];
    } Capacity:0];
}


- (void)dealloc {
    [_failBlock release];
    [_testBlock release];
    self.realArray = nil;
    [super dealloc];
}


- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if(self.testBlock(anObject))
        [self.realArray insertObject:anObject atIndex:index];
    else
        self.failBlock(anObject);
}

- (void) removeObjectAtIndex:(NSUInteger)index
{
    [self.realArray removeObjectAtIndex:index];
}

-(NSUInteger)count
{
    return [self.realArray count];
}

- (id) objectAtIndex:(NSUInteger)index
{
    return [self.realArray objectAtIndex:index];
}



-(void)errorWhileInitializing:(SEL)selector
{
    [NSException raise:@"NotSupportedInstantiation" format:@"not supported %@", NSStringFromSelector(selector)];
}
- (id)initWithArray:(NSArray *)anArray { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfFile:(NSString *)aPath{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfURL:(NSURL *)aURL{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(id)firstObj, ... { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { [self errorWhileInitializing:_cmd]; return nil;}

@end

इसका उपयोग इस तरह करें:

VSBlockTestedObjectArray *stringArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
} FailBlock:^(id element) {
    NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSString", element);
}];


VSBlockTestedObjectArray *numberArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
    return [element isKindOfClass:[NSNumber class]];
} FailBlock:^(id element) {
    NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSNumber", element);
}];


[stringArray addObject:@"test"];
[stringArray addObject:@"test1"];
[stringArray addObject:[NSNumber numberWithInt:9]];
[stringArray addObject:@"test2"];
[stringArray addObject:@"test3"];


[numberArray addObject:@"test"];
[numberArray addObject:@"test1"];
[numberArray addObject:[NSNumber numberWithInt:9]];
[numberArray addObject:@"test2"];
[numberArray addObject:@"test3"];


NSLog(@"%@", stringArray);
NSLog(@"%@", numberArray);

यह सिर्फ एक उदाहरण कोड है और वास्तविक दुनिया के आवेदन में कभी इस्तेमाल नहीं किया गया था। ऐसा करने के लिए संभवत: इसे लागू करने के लिए मोर NSArray विधि की जरूरत है।


0

यदि आप c ++ और ऑब्जेक्टिव-सी (यानी मिमी फ़ाइल प्रकार का उपयोग करते हैं) को मिलाते हैं, तो आप जोड़ी या टुपल का उपयोग करके टाइपिंग को लागू कर सकते हैं। उदाहरण के लिए, निम्न विधि में, आप एक सी ++ ऑब्जेक्ट प्रकार std :: pair बना सकते हैं, इसे OC आवरण प्रकार (std के रैपर :: जोड़ी जिसे आपको परिभाषित करने की आवश्यकता है) के ऑब्जेक्ट में कनवर्ट करें, और फिर इसे कुछ को पास करें अन्य OC विधि, जिसके भीतर आपको OC ऑब्जेक्ट को उपयोग करने के लिए C ++ ऑब्जेक्ट में वापस कनवर्ट करने की आवश्यकता है। OC विधि केवल OC आवरण प्रकार को स्वीकार करती है, इस प्रकार प्रकार की सुरक्षा सुनिश्चित करती है। तुम भी प्रकार, सुरक्षा के सुविधा के लिए और अधिक उन्नत सी ++ सुविधाओं का लाभ उठाने के लिए टपल, वैरेडिक टेम्पलेट, टाइपिस्ट का उपयोग कर सकते हैं।

- (void) tableView:(UITableView*) tableView didSelectRowAtIndexPath:(NSIndexPath*) indexPath
{
 std::pair<UITableView*, NSIndexPath*> tableRow(tableView, indexPath);  
 ObjCTableRowWrapper* oCTableRow = [[[ObjCTableRowWrapper alloc] initWithTableRow:tableRow] autorelease];
 [self performSelector:@selector(selectRow:) withObject:oCTableRow];
}

0

मेरे दो सेंट थोड़ा "क्लीनर" होंगे:

टाईपडेफ़ का उपयोग करें:

typedef NSArray<NSString *> StringArray;

कोड में हम कर सकते हैं:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.