Macro for Objective C - objective-c

I want to write a macro to generate 2 methods like below:
- (NSString*)propertyName
{
NSString *key = #"PropertyName";
//get value from NSUserDefaults
//...
}
- (void)setPropertyName:(NSString*)value
{
NSString *key = #"PropertyName";
//set value to NSUserDefaults
//...
}
The first letter of property name is lower case in the get method, upper case in the set method and key value.
The macro should receive 1 or 2 arguments which is the property name:
MY_PROPERTY(propertyName)
or
MY_PROPERTY(PropertyName)
or
MY_PROPERTY(propertyName, PropertyName)
The argument is also the value for key (string value).
How to write a macro for this? I prefer the first or second one. Thanks.

Let's get mad:
#define STRINGIFY(__TEXT__) [NSString stringWithFormat:#"%s", (#__TEXT__)]
#define GENERATE_GETTER_AND_SETTER(__UPPER_CASE__, __LOWER_CASE__, __TYPE__) \
- (void)set##__UPPER_CASE__:(__TYPE__)__LOWER_CASE__ { \
NSString *propertyName = STRINGIFY(__UPPER_CASE__); \
\
...
} \
\
- (__TYPE__)__LOWER_CASE__ { \
NSString *propertyName = STRINGIFY(__UPPER_CASE__); \
\
...
return ... \
} \
Usage:
GENERATE_GETTER_AND_SETTER(MyProperty, myProperty, NSArray*)
Note that you have to specify both lower case and upper case names and you have to know the type of the property.
It might be easier to declare the properties as #dynamic and then implement the methods dynamically, see Objective-C forwardInvocation: for more details.

Related

'VA_ARGS', an invalid preprocessing token in macros Obj-C

I use following technique to manage my logs. I print logs to asl_log and before, regards to flag [DebugManager shared] isDebugging I want to send log line to other class (method addLogEvent)
#if !defined(TheLog)
#define TheLog(fmt, ...) { \
if ([[DebugManager shared] isDebugging]) \
addLogEvent(__PRETTY_FUNCTION__,fmt,##__VA_ARGS__); \
}
#endif
#define __AF_MAKE_LOG_FUNCTION(LEVEL, NAME) \
inline void NAME(NSString *format, ...)\
{ \
TheLog(__PRETTY_FUNCTION__,format,##__VA_ARGS__);\
va_list arg_list; \
va_start(arg_list, format); \
NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; \
asl_add_log_file(NULL, STDERR_FILENO); \
asl_log(NULL, NULL, (LEVEL), "PREFIX: %s", [formattedString UTF8String]); \
va_end(arg_list); \
}
// Something has failed.
__AF_MAKE_LOG_FUNCTION(ASL_LEVEL_ERR, AFLogError)
// Something is amiss and might fail if not corrected.
__AF_MAKE_LOG_FUNCTION(ASL_LEVEL_WARNING, AFLogWarning)
// The lowest priority for user log
__AF_MAKE_LOG_FUNCTION(ASL_LEVEL_INFO, AFLogDebug)
I map log level with __AF_MAKE_LOG_FUNCTION(LEVEL, NAME) and I need to call TheLog(__PRETTY_FUNCTION__,format,##__VA_ARGS__);\ from inline void NAME(NSString *format, ...)
I get an error:
Pasting formed ',__VA_ARGS__', an invalid preprocessing token
How can I fetch ,__VA_ARGS__ and __PRETTY_FUNCTION__?
This line:
TheLog(__PRETTY_FUNCTION__,format,##__VA_ARGS__);\
is part of the definition of this macro:
#define __AF_MAKE_LOG_FUNCTION(LEVEL, NAME) \
Note that that macro does not take a variable argument list. Therefore, there's no __VA_ARGS__ defined within its definition.
The fact that the function being defined by an instantiation of __AF_MAKE_LOG_FUNCTION — the inline void NAME() — takes a variable argument list isn't relevant. If that function wants to pass the variable argument list along to another function, it needs to do it using the stdarg functionality, as it does for -[NSString initWithFormat:arguments:], but that doesn't work for your TheLog macro, because it's not designed to accept a va_list.
You can't do what you're attempting. Your TheLog macro is incompatible with how you're trying to use it. You would need to design an alternative version, such as:
#define TheLogv(fmt, args) { \
if ([[DebugManager shared] isDebugging]) \
addLogEventv(__PRETTY_FUNCTION__,fmt,args); \
}
Note that this would, in turn, require the existence of a function addLogEventv() which accepts a va_list instead of an actual variable argument list. Within the body of the function being defined by __AF_MAKE_LOG_FUNCTION, you'd have to start and end the list twice, once around each time you pass it to another function, because each function will "consume" it:
#define __AF_MAKE_LOG_FUNCTION(LEVEL, NAME) \
inline void NAME(NSString *format, ...)\
{ \
va_list arg_list; \
va_start(arg_list, format); \
TheLogv(__PRETTY_FUNCTION__,format,arg_list);\
va_end(arg_list); \
va_start(arg_list, format); \
NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; \
asl_add_log_file(NULL, STDERR_FILENO); \
asl_log(NULL, NULL, (LEVEL), "PREFIX: %s", [formattedString UTF8String]); \
va_end(arg_list); \
}
You could also change your TheLog() macro to take an NSString* and simply pass in the formattedString that's already being created.

Get a reference to class object from uninitialized variable - not instantiated object

Let's say I have an uninitialized variable:
UIViewController *vc;
From this variable, I want to reference UIViewController, such that I could call alloc or new on it to return an instantiated object.
Essentially, I want to do:
UIViewController *vc = [*typeof(vc) new];
... which does not compile because the compiler expects an expression rather than a type.
If #encode returned the actual type, I could do something like:
UIViewController *vc = [NSClassFromString(#(#encode(*typeof(vc)))) new];
...however, #encode returns '#', which just means "generic object".
I realize it's a little philosophical in nature, but I get tired of typing and would like to make a macro like NEW(x). I also realize a similar macro could be made if it involves the actual declaration, but I am not satisfied with that syntax/angle.
Here's what I have... it doesn't seem ideal, since it makes a performance hit. Still looking for better answers.
static Class classFromEncoding(const char *encoding) {
char *className = strndup(encoding + 1, strchr(encoding, '=') - encoding - 1);
Class retval = NSClassFromString(#(className));
free(className);
return retval;
}
#define NEW(variable) [classFromEncoding(#encode(typeof(*variable))) new]
Here's a macro-only version:
#define CLASS_FROM_VARIABLE(variable) \
^(const char *encoding) { \
char *className = strndup(encoding + 1, strchr(encoding, '=') - encoding - 1); \
Class retval = NSClassFromString(#(className)); \
free(className); \
return retval; \
}(#encode(typeof(*variable)))
#define NEW(variable) [CLASS_FROM_VARIABLE(variable) new]
#define ALLOC(variable) [CLASS_FROM_VARIABLE(variable) alloc]
Variations can be made using objc_getClass() or NSString initWithBytes, perhaps with performance gains. Still, it's not a no-op, which is what I'd prefer.
It's [obj class] or [obj className] depending on your needs.

How to check a typedef'd obj in Objective-c NSDictionary

I've got an method that takes NSDictionary arg. This NSDictionary has some predefined keys it'll take. All the obj's should be strings. But only certain string objs are valid for each key.
So my approach was to typedef NSString for each valid string per key. I'm hoping not to extend the NSString class.
I've typedef'd some NSString's...
typedef NSString MyStringType
Then I define a few...
MyStringType * const ValidString = #"aValidString";
Here's what I'd like to do in my sample method..
- (void)setAttrbiutes:(NSDictionary *)attributes {
NSArray *keys = [attributes allKeys];
for (NSString *key in keys) {
if ([key isEqualToString:#"ValidKey"]) {
id obj = [attributes objectForKey:key];
//Here's where I'd like to check..
if (**obj is MyStringType**) {
}
}
}
}
I'm open to other ideas if there's a better approach to solve the obj type problem of an NSDictionary.
Doesn't work like that; typedefs are a compile time alias that don't survive being passed through a dictionary.
In any case, using typedefs for something like this would be unwieldy.
I suggest you create a property list -- either as a file in your project or in code -- that contains the specifications of your various keys and valid values, then write a little validator that, passed a string and value, can validate the string-value pair for validity.
This also gives you the flexibility to extend your validator in the future. For example, you might have a #"Duration" key that can only be in the range of 1 to 20.
Instead of setting up a typedef for you special values, one possible option would be to create an NSSet of the special values. Then in your code you can verify that the object in the dictionary is in your set.
What about a combination of category on NSString + associated object?
Something along the lines (untested!!):
#interface NSString (BBumSpecial)
- (NSString *) setSpecial: (BOOL) special ;
- (BOOL) special ;
#end
and:
#implementation NSString (BBumSpecial)
static void * key ;
- (NSString *) setSpecial: (BOOL) special {
objc_setAssociatedObject(self, &key, special ? #YES : #NO, OBJC_ASSOCIATION_ASSIGN) ;
return self ;
}
- (BOOL) special {
id obj = objc_getAssociatedObject(self, &key) ;
return obj && [obj boolValue] ;
}
#end
Which you could then use as:
NSString * mySpecialString = [#"I'm Special" setSpecial:YES] ;
?

Creating a macro to perform default init

I have a lot of methods that repeat this simple boilerplate:
- (id)myObject {
if(!_myObject) {
self.myObject = [_myObject.class new];
}
return _myObject;
}
So I want to replace this with a simple macro:
#define default_init(instance) \
if(!instance) instance = [instance.class new]; \
return instance;
So that I would only have to call:
- (id)myObject {
default_init(_myObject);
}
The above code currently compiles, but the issue is that the macro directly sets the instance variable's value. Instead, I'd like to call self.instance = value;
So instead of
if(!instance) instance = [instance.class new];
I'd like something like;
if(!instance) self.instance = [instance.class new];
But obviously the current code does not allow for this. How might I accomplish something like this?
With this macro:
#define default_init(class, instance) \
if ( ! _##instance ) { \
self.instance = [class new] ; \
} \
return _##instance
I was able to create this instance method:
- (NSMutableArray*) myObject {
default_init(NSMutableArray, myObject) ;
}
I had to add a parameter defining the class, because _myObject is still nil, therefore _myObject.class is nil.
This StackOverflow question and this Cprogramming page recommend wrapping your multi-line macro in do {...} while(0):
#define default_init(class, instance) \
do { \
if ( ! _##instance ) { \
self.instance = [class new] ; \
} \
return _##instance ; \
} while(0)
If you really wanted to, you could make a macro that defines the entire method:
#define default_getter(class, instance) \
- (class*) instance { \
if ( ! _##instance ) { \
self.instance = [class new] ; \
} \
return _##instance ; \
}
And use it thusly:
default_getter(NSMutableArray, myObject)
Instead of a macro to build a getter method, I usually declare the property:
Instance.h
#interface Instance : NSObject
#property NSMutableArray* myObject ;
#end
and override - init to initialize the property:
Instance.m
- (id) init {
self = [super init] ;
if ( self ) {
self.myObject = [NSMutableArray new] ;
}
return self ;
}
Ok, this is my take on it.
Note: this was done just to see if it could be done. I don't think it would be a good idea to use this in shipping code.
First import #import "EXTRuntimeExtensions.h" from libextobjc. Then:
#define underize(name) _##name
#define property_type(property) \
property_attr(property)->objectClass
#define property_attr(propertyName) \
ext_copyPropertyAttributes(class_getProperty(self.class, # propertyName))
#define default_init(propertyName) \
- (id)propertyName { \
if(!underize(propertyName)) self.propertyName = [property_type(propertyName) new]; \
return underize(propertyName); \
} \
Then say you have a property:
#property (nonatomic) NSArray *myArray;
You can do:
default_init(myArray);
And that creates a default getter.

__VA_ARGS__ Macro expansion

I'm trying to get my macro to work like NSLog() which accepts variable arguments. Code below causes parse issues.
What is the correct way to define this?
#define TF_CHECKPOINT(f, ...) \
do { \
NSString *s = [[NSString alloc] initWithFormat:f arguments:__VA_ARGS__] autorelease]; \
[TestFlight passCheckpoint:[NSString stringWithFormat:#"%#: %#", [self class], s]]; \
} while (0)
You forgot the opening bracket for the autorelease message.
Moreover -[NSString initWithFormat:arguments:] expects a va_list argument, whereas __VA_ARGS__ is replaced by all the passed arguments. Here, you need to use -[NSString initWithFormat:] or +[NSString stringWithFormat:].
Finally, you may prefix __VA_ARGS__ with ##. By doing so, the preceding comma is deleted when there is no argument.
Try this:
#define TF_CHECKPOINT(f, ...) \
do { \
NSString *s = [NSString stringWithFormat:(f), ##__VA_ARGS__]; \
[TestFlight passCheckpoint:[NSString stringWithFormat:#"%#: %#", [self class], s]]; \
} while (0)