Adopting os_log APIs while keeping backward compatibility - ios

I'm trying to add support for the new logging and activity tracing APIs to a library in a way that maintains backward compatibility for users of the library who haven't yet adopted the latest version of the OS (iOS or macOS). I'm defining custom logging macros for each level of logging, and then for older OSes, falling back to NSLog. I've gotten this working, with one problem.
The new APIs require you to mark any non-constant, non-scalar values as explicitly public if you want them to show up in log output. This is what an invocation of my macro looks like:
UZKLogInfo("Reading file %{public}# from archive", fileName);
This compiles fine with the SDK that includes os_log (e.g. iOS 10.0 or later), but when I compile with an earlier version so my macro falls back to NSLog, I get a compiler warning:
Using 'public' format specifier annotation outside of os_log()/os_trace()
And the log line printed looks like this:
Reading file <decode: missing data> from archive
This is a simplified version of my macro definition (only including the info definition and simplifying the conditional:
#if UNIFIED_LOGGING_SUPPORTED
#import os.log;
#define UZKLogInfo(format, ...) os_log_info(OS_LOG_DEFAULT, format, ##__VA_ARGS__);
#else // Fall back to regular NSLog
#define UZKLogInfo(format, ...) NSLog(#format, ##__VA_ARGS__);
#endif
Is there any way to strip the "{public}" text (some kind of string replacement?) from format in the fallback case? Or is there another way to support the old and new APIs without giving up the level of info I've always shown in the logs? I need to use a macro (according the last year's WWDC session on the topic, or else I lose the call site metadata.

I chose to do an NSString replace in the macro, and to suppress the compiler warnings as part of it, so it could be done for each line, rather than globally for the whole file or project. It looks like this:
#if UNIFIED_LOGGING_SUPPORTED
#import os.log;
#define UZKLogInfo(format, ...) os_log_info(OS_LOG_DEFAULT, format, ##__VA_ARGS__);
#else // Fall back to regular NSLog
#define _removeLogFormatTokens(format) [#format stringByReplacingOccurrencesOfString:#"{public}" withString:#""]
#define _stringify(a) #a
#define _nsLogWithoutWarnings(format, ...) \
_Pragma( _stringify( clang diagnostic push ) ) \
_Pragma( _stringify( clang diagnostic ignored "-Wformat-nonliteral" ) ) \
_Pragma( _stringify( clang diagnostic ignored "-Wformat-security" ) ) \
NSLog(_removeLogFormatTokens(format), ##__VA_ARGS__); \
_Pragma( _stringify( clang diagnostic pop ) )
#define UZKLogInfo(format, ...) _nsLogWithoutWarnings(format, ##__VA_ARGS__);
#endif
It's called like so:
UZKLogInfo("Message: %#", anObjectToLog);

Related

Warning equivalent of #pragma poison

I use the crash function for testing Crashlytics integrations, but I obviously never want to ship that code in an app.
It's possible to poison identifiers so that any current or future use causes an error:
#pragma GCC poison crash
Is there an equivalent #pragma directive that emits a warning when an identifier is used? I want to be able to build the codebase while retaining a visible indicator that attention is required.
This should work, now that _Pragma is available. Instead of using #pragma GCC poison, you can just #define the identifier crash in a way which will generate a warning using #pragma GCC warning:
#define DO_PRAGMA(x) _Pragma(#x)
#define WARN(x) DO_PRAGMA(GCC warning #x)
#define crash WARN("crash" used) crash
The first two macros just make it less work to escape quotation marks. Note that crash expands to itself (as well as the _Pragma), which works because the C preprocessor doesn't expand a token inside of its own expansion.
If you change warning to error, you'll get an error instead. You could easily arrange to change all of those by using some more macros, or you could just use -Werror
You could use #warning
Usage would be as follows:
#warning This is a custom message

Operator 'defined' requires an identifier ios

I've below code in my project dlog should print the values in console if isConsoleLogActive is YES.
It gives error like Operator 'defined' requires an identifier
#if defined ([Util isConsoleLogActive])// Operator 'defined' requires an identifier in this line
#define DLog(...) NSLog(__VA_ARGS__)
#define DTrace() NSLog(#"%s", __PRETTY_FUNCTION__)
#else
#define DLog(...) /* */
#define DTrace() /* */
#endif
if I use the same code([Util isConsoleLogActive]) in my .m it works perfectly fine. I face this issue only in #define
What could be the issue. Please give me some idea.
The various commands that start with # are preprocessor directives. These get executed before the compilation phase at build time, before your application actually executes. You should use the preprocessor directives to conditionally include different code in your application based on build configuration. The preprocessor, however, is the wrong way to handle conditional execution on a specific platform at runtime; for that, you want your standard "if...else" logic.
If your goal with that statement is to determine if the given selector exists, try respondsToSelector, instead.
Result of
[Util isConsoleLogActive]
is not known at compile-time. So you can not use it with '#if defined'.

What is the purpose of using #define to define a constant with no value?

What is the purpose of using #define to define a constant with no value?
Such as:
#define TOKEN
You can use it to enable or disable certain code using #ifdef or #ifndef, eg this:
#ifdef TOKEN
printf("token is defined");
#endif
One use for this would be to toggle logging in debug builds. Another would be include guards.
As #Nutomic says, this sort of #define is useful for enabling/disabling certain bits of code. I've seen this used to create compile-time options for a given library:
#ifdef STRICT_PARSING
//...
#endif
#ifdef APPROXIMATE_PARSING
//...
#endif
#ifdef UNICODE //this is especially useful when trying to control Unicode vs ANSI builds
//call Unicode functions here
#else
//call Ansi functions here
#endif
#ifdef USE_STL //another good one, used to activate or deactivate STL-based bits of an API; the function declarations were dependent on USE_STL being defined. A plain-C API was exposed either way, but the STL support was nice too.
std::string MyApi();
#endif

How to use preprocessors in objective c

Hi in one of my applications I have to support that app for IOS6 & IOS7.Inorder to accomplish that first I have to know the current device version. For that I had defined one macro and I am trying to using that macro as a reference to accomplish my task. The code which I wrote is as such below.
In .h file I defined IPhoneOSVersion as 50000.
This code is in .m file
if([[[UIDevice currentDevice] systemVersion] isEqualToString:#"7.0"])
{
#undef IPhoneOSVersion
#define IPhoneOSVersion 70000
NSLog(#"_IPHONE_OS_VERSION_MIN_REQUIRED after is %d",IPhoneOSVersion);
}
else
{
#undef IPhoneOSVersion
#define IPhoneOSVersion 60000
NSLog(#"_IPHONE_OS_VERSION_MIN_REQUIRED after is %d",IPhoneOSVersion);
}
NSLog(#"_IPHONE_OS_VERSION_MIN_REQUIRED after is %d",IPhoneOSVersion);
And if i run this code in IOS7. In console the data have to print like this _IPHONE_OS_VERSION_MIN_REQUIRED after is 70000 but unfortunately I am getting _IPHONE_OS_VERSION_MIN_REQUIRED after is 60000. Even I put a break points at else condition also but that is not executing but the macro value is changing.Can anyone please let me know why the macro value changing like this.
You shouldn't be hardcoding against the OS version, Apple recommended way of supporting multiple OS versions is to check for some specific class, API, protocol or function, this allows for greater flexibility as some of that stuff is sometimes backwards compatible.
Here's a pretty decent tutorial on how to check for existence of specific resources in code http://www.raywenderlich.com/42591/supporting-multiple-ios-versions-and-devices and the docs from Apple https://developer.apple.com/library/ios/documentation/developertools/conceptual/cross_development/Using/using.html
EDIT: To answer your question on why the macro is changed, the compiler goes over both branches of the if-else, thus the last declaration of the macro is used. You can't use a macro like that and change it during runtime, macros are meant to be define before compilation.
You use the preprocessor in Objective-C in exactly the same way as in C or C++. The preprocessor doesn't care about your if/else statements. It sees a sequence of #undef, #define, #undef, #define and performs them one after the other, so in your last line, the last #define is in effect. You cannot influence these #defines with anything happening at runtime.
There are always three OS versions in play: The deployment target (that is the lowest OS version where you allow your app to run), the SDK version, and the actual version at runtime. The first two you set in Xcode; the actual version is obviously out of your control except that you know it is the same or higher than the deployment target.
__IPHONE_OS_VERSION_MIN_REQUIRED = Deployment target
__IPHONE_OS_VERSION_MAX_ALLOWED = SDK version
Try with
if([[[UIDevice currentDevice] systemVersion] floatValue] == 7.0)

Literal #YES not working in iOS 5 / Xcode 4.4

New Xcode 4.4 is out and it should support literals like
#42
#"String"
#23.0L
#{ #"key" : obj } and
#[obj1, obj2]
and it should also support #YES and #NO, which isn't working when targeting latest iOS 5 (and prior).
After compiling it show the error message:
Unexpected type name 'BOOL': expected expression
I know you can fix it by typing #(YES) and #(NO). But I want to know the reason why it isn't working as expected.
The reason is Apple forgot the parentheses here:
#define YES (BOOL)1
This will be fixed in iOS 6 SDK:
#define YES ((BOOL)1)
In the meantime you must type #(YES).
This is useful for information about literals.
A commenter on this answer also points out:
There is one small thing I'd like to warn about. Literal bools are also not supported
because of this. However, a quick fix that I implemented was adding
this to the beginning of one of my common headers (in an iOS project)
#ifndef __IPHONE_6_0
#if __has_feature(objc_bool)
#undef YES
#undef NO
#define YES __objc_yes
#define NO __objc_no
#endif
#endif
#phix23s answer seems to be more to the point. You should accept that.
This was worth adding from comments:
It should be noted that this needs to be done after the #import . If one puts these #defines in their Prefix.pch, they should make sure to import Foundation earlier in the pch

Resources