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.
Related
I have Logger.h with following code:
#import <Foundation/Foundation.h>
#import <asl.h>
#define LogDebug(format, ...){ \
Logger(ASL_LEVEL_DEBUG, format,##__VA_ARGS__); }
#define Logger(LEVEL,format, ...) \
LogLocal(LEVEL,format,##__VA_ARGS__);
#define LogLocal(LEVEL, format, ...) \
va_list arg_list; \
va_start(arg_list, format); \
// ...\
va_end(arg_list); \
I call log from Obj-C as:
LogDebug(#"Name is called with flag: %#", collectName ? #"YES" : #"NO");
However I get an error:
'va_start' used in function with fixed args
How to get rid of this problem?
EDIT 1:
I tried to call also: AFLogLocal(LEVEL,format,...); - same error
EDIT 2:
if I'll remove asl_log and replace with NSLog - it will work:
#define LogLocal(LEVEL,format, ...) \
NSLog((#"XXX: %s " format), __PRETTY_FUNCTION__, ##__VA_ARGS__);
EDIT 3
credits to #Amin Negm-Awad, replaced LogLocal with function in .m file as:
void LogLocal(int level, NSString *format, ...){
va_list arg_list;
va_start(arg_list, format);
va_end(arg_list);
NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list];
asl_add_log_file(NULL, STDERR_FILENO);
asl_log(NULL, NULL, (level), "XXXX: %s", [formattedString UTF8String]);
}
works as expected.
You use macros that are expanded. But they do not create a scope or a call on expansion. It's simple text replacement.
// Somewhere in a function
void f(void)
{
…
LogDebug(#"Name is called with flag: %#", collectName ? #"YES" : #"NO");
…
}
This will expand at the end to something like this:
// Somewhere in a function
void f(void)
{
…
// LogDebug(#"Name is called with flag: %#", collectName ? #"YES" : #"NO");
va_list arg_list;
va_start(arg_list, #"Name is called with flag: %#");
// ...
va_end(arg_list);
…
}
Since f() takes no vargs, there are no vargs.
It might help to make LogLocal() a function, not a macro:
void LogLocal( int level, NSString *format, ... );
Implementation:
void LogLocal( int level, NSString *format, ... )
{
va_list arg_list;
va_start(arg_list, format);
// ...
va_end(arg_list);
}
To your Edit 2:
Yes, this works, because you do not touch the arg list, but pass the args to a function taking vargs. But you do not need that trick. Instead make LogLocal() a function and it will be called similar to NSLog() without any error.
I have a log function:
#define LOGERROR(err) if(err) { \
LOGTRACE(#"[NSError] %s (%d): (%d:%#) Reason: %#", \
__PRETTY_FUNCTION__, \
__LINE__, \
err.code, \
err.domain, \
err.localizedDescription) \
}
When I call it with
LOGERROR(playerItem.error);
I get a warning: "NSInteger should not be used as a format argument, add an explicit cast to long".
Xcode's autofix inserts %ld before LOGERROR, which is wrong.
I think this warning comes from the use of __LINE__, which according to https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html returns an int.
How can I remove the warning from this call?
Just to formalize the answer I gave in a comment:
The compiler is complaining about the use of err.code, which returns an NSInteger. Cast it to a long: (long)(err.code), and then use %ld in the format string.
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.
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.
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)