Question

I'd like to have class-level properties, and I found a solution from https://stackoverflow.com/a/15811719/157384

@interface Model
+ (int) value;
+ (void) setValue:(int)val;
@end

@implementation Model
static int value;
+ (int) value
{ @synchronized(self) { return value; } }
+ (void) setValue:(int)val
{ @synchronized(self) { value = val; } }
@end

and now you're able to call through property accessors, like so:

Model.value = 1
Model.value // => 1

Fantastic!

Now, I want to make this chunk of code reusable, in some form of macro or meta-programming, that takes a property name and type. How can we write it?

With the example above, value and (int) (and Model) should be dynamic.

Update

Thanks to @Rich I now have this:

// Common.h
#define CLASS_PROPERTY_INTERFACE(TYPE, METHOD, CMETHOD) \
+ (TYPE) METHOD; \
+ (void) set##CMETHOD:(TYPE)val; \

#define CLASS_PROPERTY_IMPLEMENTATION(TYPE, METHOD, CMETHOD) \
static TYPE _##METHOD; \
+ (TYPE) METHOD \
{ @synchronized(self) { return _##METHOD; } } \
+ (void) set##CMETHOD:(TYPE)val \
{ @synchronized(self) { _##METHOD = val; } } \

// User.h
@interface User : NSObject
CLASS_PROPERTY_INTERFACE(User *, me, Me)
@end

// User.m
@implementation User
CLASS_PROPERTY_IMPLEMENTATION(User *, me, Me)
@end

User.me = currentUser;
User.me // => currentUser

One thing left to be done is to automatically capitalize the method name passed to the macro, if at all possible.

But it's already much more succinct than the boilerplate as it stands!

Was it helpful?

Solution

The macro way...

NOTE: This is a bit horrible and I don't really recommend using this but was playing around with defining them in macros...

The caveat to this (aside from it not being very "nice") is that the setter method is of the form set_XXX: format.

#define CLASS_INTERFACE(CLS_NAME, METHOD, TYPE) @interface CLS_NAME : NSObject \
    + (TYPE) METHOD; \
    + (void) set_##METHOD:(TYPE)val; \
    @end \

#define CLASS_IMPLEMENTATION(CLS_NAME, METHOD, TYPE) @implementation CLS_NAME \
    static TYPE METHOD; \
    + (TYPE) METHOD \
    { @synchronized(self) { return METHOD; } } \
    + (void) set_##METHOD:(TYPE)val \
    { @synchronized(self) { METHOD = val; } } \
    @end \

Place the following in header files:

CLASS_INTERFACE(Test, value, int)

And this in the .m files:

CLASS_IMPLEMENTATION(Test, value, int)

Then to use the Test class:

[Test set_value:4];
int i = [Test value];

Again this is pretty horrible but would work...!

Edit:

With singletons

As mentioned in the comments I think the use of a singleton is better, which has made me write even more horrible code :(

Now we have (sick bag ready):

#define SINGLETON_INTERFACE_START(CLS_NAME) @interface CLS_NAME : NSObject \
    +(instancetype) sharedInstance; \

#define SINGLETON_INTERFACE_END(CLS_NAME) \
    @end \
    static inline CLS_NAME * CLS_NAME##Global () { return [CLS_NAME sharedInstance]; }

#define SINGLETON_IMPLEMENTATION(CLS_NAME) @implementation CLS_NAME \
    +(instancetype) sharedInstance \
    { \
        static id instance; \
        static dispatch_once_t onceToken; \
        dispatch_once(&onceToken, ^{ \
            instance = [self new]; \
        }); \
        return instance; \
    } \
    @end \

In you headers:

SINGLETON_INTERFACE_START(Test)

@property (atomic, assign) NSUInteger value;

SINGLETON_INTERFACE_END(Test)

And in your .m:

SINGLETON_IMPLEMENTATION(Test)

And to use this (yes more sick can be produced):

TestGlobal().value = 1;
int i = TestGlobal().value;

Or the "nicer" Objective-C way:

[Test sharedInstance].value = 1;
int i = [Test sharedInstance].value;

You could even (Team America amounts of sick right now) have a #define for the properties in the interface.

Note that I've left the @property definitions in the @interface as atomic because the OP seems to love using @synchronized. This is not needed is they are set to atomic.

And I know the OP wants class "properties" (just saying that makes me shiver), but there are other better options!

OTHER TIPS

This is no different than asking to dynamically generate accessors on a class to be used by instances. Same set of issues, in fact.

It isn't that hard to do for straight object values, regardless of the type of object, and you can generate them purely at runtime by leveraging associated objects.

But the requirement that you generate these with type information beyond id means that you've shifted the problem to be a compile time problem.

For that, you are pretty much left with writing a series of #define macros that would generate whatever code you need. You'll need at least two macros; one for the implementation and one for the interface.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top