1

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!

Community
  • 1
  • 1
kenn
  • 3,303
  • 2
  • 29
  • 42
  • Can you say more about what you mean by making it reusable, perhaps using pseudo-code? Are you looking for something like [`class_addProperty`](https://developer.apple.com/library/ios/documentation/cocoa/reference/ObjCRuntimeRef/Reference/reference.html#//apple_ref/c/func/class_addProperty)? – Aaron Brager Apr 24 '14 at 21:09
  • I'd say just use an Xcode code snippet for this, rather than meta-programming, but if you really want a macro, look to [libextobjc](https://github.com/jspahrsummers/libextobjc/blob/experimental/extobjc/EXTPassthrough.h) for inspiration. – jscs Apr 24 '14 at 21:11
  • Is it possible to have a `@interface` and `@implementation` in the same header file anyway? – Rich Apr 24 '14 at 21:13
  • They can be in the same file without a problem, @Rich. Putting the `@implementation` section into a file that gets imported into multiple other files will cause linker errors, though. – jscs Apr 24 '14 at 21:16
  • @JoshCaswell Sorry, that's what I meant, having the `@implementation` in a header file (which it would have to be with a macro - which would also define the `@interface`) wouldn't work, so unless you use a code snippet (or a file template) like you originally suggested I don't think he can do what he wants...! – Rich Apr 24 '14 at 21:19
  • Good point, @Rich: you wouldn't be able to split the macro across two files; I hadn't even thought of that part. – jscs Apr 24 '14 at 21:19
  • @kenn, what are you actually trying to do out of interest, it seems a tiny bit nasty! :/ – Rich Apr 24 '14 at 21:22
  • @Rich @JoshCaswell Thanks for pointing out that having `@implementation` in the header file is not viable. Snippet sounds fair but it's not DRY, right? @AaronBrager `class_addProperty` looks promising, can you come up with one? :) – kenn Apr 24 '14 at 21:46
  • `class_addProperty()` isn't going to help. – bbum Apr 24 '14 at 22:25
  • are you doing this for fun or because you have a "need" for it? – Nick Apr 24 '14 at 22:51
  • Very odd thing to like to have... – race_carr Apr 25 '14 at 00:15
  • I've made it worse with a singleton method :( It makes me sad. God knows what he's doing - he won't say..! – Rich Apr 25 '14 at 01:17

2 Answers2

2

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.

NSGod
  • 22,699
  • 3
  • 58
  • 66
bbum
  • 162,346
  • 23
  • 271
  • 359
  • Thanks for pointing that out. I've updated the question with a tentative solution but it requires to pass both a method name and a capitalized method name. It's not possible to capitalize a passed argument inside the macro I assume? – kenn Apr 25 '14 at 00:08
2

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!

Community
  • 1
  • 1
Rich
  • 8,108
  • 5
  • 46
  • 59
  • The whole point of it is to avoid `[Test set_value:4]`, but use `Test.value = 4`. So we need to tweak the case for the setter method? – kenn Apr 24 '14 at 23:26
  • You really shouldn't be calling `.` for a setter on a non-property class method. While it will work it looks vile! There's no way of using macros to do what you want I don't think unless it has 2 parameters `value`, `setValue` for the macro, I could update the answer to show that if you like. Why can't you use a singleton instead of this odd class method way of doing it by the way? Everything would be much better then...! – Rich Apr 24 '14 at 23:40
  • And I don't suggest using singletons often unless you really need to go down that road, single getter/setter method pairs would be better as class methods, but if you want something dynamic you can send messages to singletons might be a better idea. – Rich Apr 24 '14 at 23:48
  • Yeah, I've updated the question with a tentative solution - still need to pass two method names, one for the getter and another for setter. Pretty close now. :) – kenn Apr 25 '14 at 00:10
  • @kenn I've updated my answer with the singleton method which is a _very_ small bit better. Least you're using the `.` syntax correctly then! And you don't have to provide two method names. – Rich Apr 25 '14 at 01:16