उद्देश्य-सी: श्रेणी में संपत्ति / उदाहरण चर


122

जैसा कि मैं उद्देश्य-सी में एक श्रेणी में संश्लेषित संपत्ति नहीं बना सकता, मुझे नहीं पता कि निम्नलिखित कोड को कैसे अनुकूलित किया जाए:

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

@implementation MyClass (Variant)

@dynamic test;

- (NSString *)test {
    NSString *res;
    //do a lot of stuff
    return res;
}

@end

परीक्षण विधि क्रम पर कई बार कहा जाता है और मैं परिणाम की गणना करने के लिए सामान की एक बहुत कुछ कर रहा हूँ। आम तौर पर एक संश्लेषित संपत्ति का उपयोग करके मैं पहली बार पद्धति को कहा जाता है, तो IVar _test में मान संग्रहीत करता हूं और अगली बार इस IVar को वापस कर रहा हूं। मैं उपरोक्त कोड कैसे अनुकूलित कर सकता हूं?


2
ऐसा क्यों न करें जो आप सामान्य रूप से करते हैं, केवल एक श्रेणी के बजाय, संपत्ति को MyClass आधार वर्ग में जोड़ें? और इसे आगे बढ़ाने के लिए, पृष्ठभूमि पर अपने भारी सामान का प्रदर्शन करें और प्रक्रिया को आग लगा दें या प्रक्रिया पूरी होने पर MyClass के लिए किसी हैंडलर को कॉल करें।
जेरेमी

4
MyClass Core Data से उत्पन्न वर्ग है। यदि मैं लेकिन मेरा कस्टम ऑब्जेक्ट कोड जनरेट किए गए वर्ग के अंदर है तो यह गायब हो जाएगा यदि मैं अपने कोर डेटा से कक्षा को पुनः प्राप्त करता हूं। इस वजह से, मैं एक श्रेणी का उपयोग कर रहा हूं।
धाम

1
शायद उस सवाल को स्वीकार करें जो शीर्षक पर सबसे अच्छा लागू होता है? ("श्रेणी में संपत्ति")
hfossli

सिर्फ उपवर्ग ही क्यों न बनाएं?
स्कॉट झू

जवाबों:


124

@ लोरियन की विधि काम करेगी (नोट: उत्तर अब हटा दिया गया है) , लेकिन आपके पास केवल एक ही संग्रहण स्लॉट होगा। इसलिए यदि आप इसे कई उदाहरणों पर उपयोग करना चाहते हैं और प्रत्येक उदाहरण का एक अलग मूल्य है, तो यह काम नहीं करेगा।

सौभाग्य से, ऑब्जेक्टिव-सी रनटाइम में इस चीज़ को एसोसिएटेड ऑब्जेक्ट्स कहा जाता है जो वास्तव में वही कर सकता है जो आप चाहते हैं:

#import <objc/runtime.h>

static void *MyClassResultKey;
@implementation MyClass

- (NSString *)test {
  NSString *result = objc_getAssociatedObject(self, &MyClassResultKey);
  if (result == nil) {
    // do a lot of stuff
    result = ...;
    objc_setAssociatedObject(self, &MyClassResultKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  }
  return result;
}

@end

एसोसिएटेड ऑब्जेक्ट्स के लिए निश्चित लिंक: developer.apple.com/library/ios/#documentation/cocoa/Conceptual/…
MechEthan

5
@DaveDeLong इस समाधान के लिए धन्यवाद! बहुत सुंदर नहीं है, लेकिन यह काम करता है :)
drm

42
"बहुत सुंदर नहीं"? यह उद्देश्य-सी की सुंदरता है! ;)
डेव डोंगलांग

6
बहुत बढ़िया जवाब! तुम भी @selector(test)एक कुंजी के रूप में उपयोग कर स्थिर चर से छुटकारा पा सकते हो , जैसा कि यहाँ बताया गया है: stackoverflow.com/questions/16020918/…
गैब्रियल पेट्रोनेला

1
@HotFudgeSunday - लगता है कि यह तेजी से काम करता है: stackoverflow.com/questions/24133058/…
स्कॉट Corscadden

174

ज-फ़ाइल

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

.m फ़ाइल

#import <objc/runtime.h>

static void * LaserUnicornPropertyKey = &LaserUnicornPropertyKey;

@implementation NSObject (LaserUnicorn)

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, LaserUnicornPropertyKey);
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, LaserUnicornPropertyKey, unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

@end

एक सामान्य संपत्ति की तरह - डॉट-नोटेशन के साथ सुलभ

NSObject *myObject = [NSObject new];
myObject.laserUnicorn = [LaserUnicorn new];
NSLog(@"Laser unicorn: %@", myObject.laserUnicorn);

आसान वाक्यविन्यास

वैकल्पिक रूप से आप @selector(nameOfGetter)इसके बजाय एक स्थिर सूचक कुंजी बनाने के बजाय उपयोग कर सकते हैं :

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, @selector(laserUnicorn));
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, @selector(laserUnicorn), unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

अधिक जानकारी के लिए https://stackoverflow.com/a/16020927/202451 देखें


4
अच्छा लेख। ध्यान देने वाली एक बात लेख में अपडेट है। 22 दिसंबर, 2011 को अपडेट करें: यह ध्यान रखना महत्वपूर्ण है कि एसोसिएशन के लिए कुंजी एक शून्य सूचक शून्य * कुंजी है, न कि एक स्ट्रिंग। इसका मतलब है कि जब एक संबंधित संदर्भ प्राप्त करते हैं, तो आपको रनटाइम के लिए ठीक उसी सूचक को पास करना होगा। यह इस उद्देश्य के अनुसार काम नहीं करेगा यदि आपने कुंजी के रूप में सी स्ट्रिंग का उपयोग किया है, तो स्ट्रिंग को स्मृति में किसी अन्य स्थान पर कॉपी किया और कुंजी के रूप में कॉपी किए गए स्ट्रिंग को सूचक को पास करके संबंधित संदर्भ तक पहुंचने का प्रयास किया।
श्री रोजर्स

4
आप वास्तव में जरूरत नहीं है @dynamic objectTag;@dynamicइसका मतलब है कि सेटर और गेट्टर कहीं और उत्पन्न होगा, लेकिन इस मामले में वे यहीं लागू होते हैं।
इलुआटोव

@ सच सच! फिक्स्ड!
hfossli

1
मैनुअल मेमोरी प्रबंधन के लिए लेजर यूनिकॉर्न के सौदे के बारे में कैसे? क्या यह स्मृति रिसाव है?
मैन्नी

स्वचालित रूप से जारी किया गया है stackoverflow.com/questions/6039309/…
hfossli

32

दिया गया उत्तर बहुत अच्छा काम करता है और मेरा प्रस्ताव इसका केवल एक विस्तार है जो बहुत अधिक बॉयलरप्लेट कोड लिखने से बचता है।

श्रेणी के गुणों के लिए बार-बार गेटटर और सेटर के तरीकों को लिखने से बचने के लिए यह उत्तर मैक्रोज़ का परिचय देता है। इसके अतिरिक्त ये मैक्रो आदिम प्रकार के गुणों जैसे कि intया के उपयोग को आसान बनाते हैं BOOL

मैक्रों के बिना पारंपरिक दृष्टिकोण

परंपरागत रूप से आप एक श्रेणी की संपत्ति जैसे परिभाषित करते हैं

@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end

तो फिर तुम एक का उपयोग कर एक गेटर और सेटर विधि लागू करने की आवश्यकता जुड़े वस्तु और चयनकर्ता प्राप्त कुंजी (के रूप में मूल उत्तर देखें ):

#import <objc/runtime.h>

@implementation MyClass (Category)
- (NSString *)text{
    return objc_getAssociatedObject(self, @selector(text));
}

- (void)setText:(NSString *)text{
    objc_setAssociatedObject(self, @selector(text), text, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

मेरा सुझाव दिया दृष्टिकोण

अब, मैक्रो का उपयोग करके आप इसके बजाय लिखेंगे:

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end

मैक्रोज़ को निम्नलिखित के रूप में परिभाषित किया गया है:

#import <objc/runtime.h>

#define CATEGORY_PROPERTY_GET(type, property) - (type) property { return objc_getAssociatedObject(self, @selector(property)); }
#define CATEGORY_PROPERTY_SET(type, property, setter) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), property, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
#define CATEGORY_PROPERTY_GET_SET(type, property, setter) CATEGORY_PROPERTY_GET(type, property) CATEGORY_PROPERTY_SET(type, property, setter)

#define CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(type, property, valueSelector) - (type) property { return [objc_getAssociatedObject(self, @selector(property)) valueSelector]; }
#define CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(type, property, setter, numberSelector) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), [NSNumber numberSelector: property], OBJC_ASSOCIATION_RETAIN_NONATOMIC); }

#define CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(unsigned int, property, unsignedIntValue)
#define CATEGORY_PROPERTY_SET_UINT(property, setter) CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(unsigned int, property, setter, numberWithUnsignedInt)
#define CATEGORY_PROPERTY_GET_SET_UINT(property, setter) CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_SET_UINT(property, setter)

मैक्रो CATEGORY_PROPERTY_GET_SETदी गई संपत्ति के लिए एक गेट्टर और सेटर जोड़ता है। केवल पढ़ने के लिए या लिखने-ही गुण का उपयोग करेगा CATEGORY_PROPERTY_GETऔरCATEGORY_PROPERTY_SET क्रमशः मैक्रो का ।

आदिम प्रकारों पर थोड़ा अधिक ध्यान देने की आवश्यकता है

चूंकि आदिम प्रकार कोई वस्तु नहीं हैं, इसलिए उपरोक्त मैक्रो में unsigned intसंपत्ति के प्रकार के रूप में उपयोग करने के लिए एक उदाहरण है । किसी NSNumberऑब्जेक्ट में पूर्णांक मान को लपेटकर ऐसा करता है । तो इसका उपयोग पिछले उदाहरण के अनुरूप है:

@interface ...
@property unsigned int value;
@end

@implementation ...
CATEGORY_PROPERTY_GET_SET_UINT(value, setValue:)
@end

इस पैटर्न के बाद, आप बस समर्थन के लिए और अधिक मैक्रो जोड़ सकते हैं signed int,BOOL , आदि ...

सीमाएं

  1. सभी मैक्रोज़ OBJC_ASSOCIATION_RETAIN_NONATOMICडिफ़ॉल्ट रूप से उपयोग कर रहे हैं ।

  2. ऐप कोड जैसी आईडीई वर्तमान में संपत्ति के नाम को रीफ़ैक्‍ट करने पर सेटर के नाम को नहीं पहचानती है। आपको इसका नाम बदलकर स्वयं करना होगा।


1
#import <objc/runtime.h>अन्यथा .m फ़ाइल श्रेणी में मत भूलना । संकलित समय त्रुटि: फ़ंक्शन का अस्पष्ट घोषणा 'objc_getAssociatedObject' C99 में अमान्य हैstackoverflow.com/questions/9408934/…
साठे_नगराजा

7

बस libextobjc लाइब्रेरी का उपयोग करें :

h-फ़ाइल:

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

एम-फ़ाइल:

#import <extobjc.h>
@implementation MyClass (Variant)

@synthesizeAssociation (MyClass, test);

@end

@SynthesizeAssociation के बारे में अधिक


इसके साथ एक एक्सेसर को ओवरराइड करने का सही तरीका क्या है (जैसे आलसी किसी प्रॉपर्टी को लोड करता है)
डेविड जेम्स

3

केवल IOS 9 उदाहरण के साथ परीक्षण किया गया: UINavigationBar (श्रेणी) के लिए एक UIView संपत्ति जोड़ना

UINavigationBar + Helper.h

#import <UIKit/UIKit.h>

@interface UINavigationBar (Helper)
@property (nonatomic, strong) UIView *tkLogoView;
@end

UINavigationBar + Helper.m

#import "UINavigationBar+Helper.h"
#import <objc/runtime.h>

#define kTKLogoViewKey @"tkLogoView"

@implementation UINavigationBar (Helper)

- (void)setTkLogoView:(UIView *)tkLogoView {
    objc_setAssociatedObject(self, kTKLogoViewKey, tkLogoView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)tkLogoView {
    return objc_getAssociatedObject(self, kTKLogoViewKey);
}

@end

-2

एक और संभव समाधान, शायद आसान, जो उपयोग नहीं करता Associated Objectsहै वह श्रेणी कार्यान्वयन फ़ाइल में एक चर को निम्नानुसार घोषित करना है:

@interface UIAlertView (UIAlertViewAdditions)

- (void)setObject:(id)anObject;
- (id)object;

@end


@implementation UIAlertView (UIAlertViewAdditions)

id _object = nil;

- (id)object
{
    return _object;
}

- (void)setObject:(id)anObject
{
    _object = anObject;
}
@end

इस तरह के कार्यान्वयन का नकारात्मक पक्ष यह है कि ऑब्जेक्ट एक उदाहरण चर के रूप में कार्य नहीं करता है, बल्कि एक वर्ग चर के रूप में कार्य करता है। साथ ही, संपत्ति के गुण निर्दिष्ट नहीं किए जा सकते (जैसे कि OBJC_ASSOCIATION_RETAIN_NONATOMIC जैसी एसोसिएटेड ऑब्जेक्ट में उपयोग किए गए)


सवाल उदाहरण चर के बारे में पूछता है। आपका समाधान लोरेन की तरह है
hfossli

1
वास्तव में?? क्या आप जानते हैं कि आप क्या कर रहे हैं? आपने एक आंतरिक चर पर पहुंचने के लिए गेट्टर / सेटर विधि की एक जोड़ी बनाई जो एक फ़ाइल के लिए स्वतंत्र है एक वर्ग वस्तु, लेकिन, यदि आप दो UIAlertView वर्ग ऑब्जेक्ट आवंटित करते हैं, तो उनके ऑब्जेक्ट के मान समान होते हैं!
इताची
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.