QUESTION
I would like to trigger a warning only if my TESTING is YES. Is this possible? What I have now doesn't work. What should I do?
BOOL const TESTING = NO;
#if TESTING == YES
#warning don't forget to turn testing to NO before upload
#endif
ANSWER
Based on the answer below, here is what worked for me:
#define _TESTING // COMMENT THIS OUT TO DISABLE TESTING MODE
#ifdef _TESTING
BOOL const TESTING = YES;
#warning don't forget to turn testing to NO for production
#else
BOOL const TESTING = NO;
#endif
Try replacing what you have with
#ifdef TESTING
#warning //... warning here
BOOL const testingBool = YES;
#else
BOOL const testingBool = NO;
#endif
Then, you need to add TESTING as a "Preprocessor Macro" in your Target's Build Settings (see this question for more details on how to do that).
Related
In my project, I am sharing events to Firebase Analytics and other analytics platform
When an event occurs I am checking whether the scheme is in debug mode:
#ifdef DEBUG
#define sharedAnalytics NO
#else
#define sharedAnalytics YES
#endif
When debugging in Xcode evetything seems to work as expected:
-When changing the build configuration in scheme to Release, sharedAnalytics is true, and the event is fired,
when changing the build configuration to Debug - sharedAnalytics is false.
But the app in Appstore stopped firing those events somewhere in Jan 2020, both in Firebase Analytics and in AppsFlyer.
In the Android app everything works.
From what I know, when the app is compiled for Appstore (or Testflight) it automatically switches to Release build configuration, but it looks like it's not?
My questions are:
-Do I need to make sure I am in Release mode before uploading an app to Appstore?
-What changed in the beginning of 2020?
Thanks!
EDIT:
This is where I check sharedAnalytics
- (void)logFirebaseEvent:(NSString*)eventName params:(NSDictionary*)params{
if (sharedAnalytics) {
[FIRAnalytics logEventWithName:eventName parameters:params];
}
else {
NSLog(#"%#", sharedAnalytics == nil ? #"sharedAnalytics is nil" : #"sharedAnalytics == NO");
}
}
When in DEBUG prints "sharedAnalytics is nil"
When in RELEASE prints logs the event.
Debug, Release, Appstore is just build configurations.
Xcode by default just create Debug and Release configurations in new projects.
There is two possible ways to define macros:
in code by #define ...
in build settings in GCC_PREPROCESSOR_DEFINITIONS parameter
May be you have copied Appstore from Debug or may be you have #define DEBUG some where
May be you have DEBUG=0
Proper way to check for DEBUG is
#if defined(DEBUG) && DEBUG
#define sharedAnalytics NO
#else
#define sharedAnalytics YES
#endif
May be you also checking sharedAnalytics wrong.
In both cases #ifdef sharedAnalytics gives YES
Ok Vadim, not that this will solve the problem, but when you check this is a bit better
- (void)logFirebaseEvent:(NSString*)eventName params:(NSDictionary*)params{
if (sharedAnalytics) {
[FIRAnalytics logEventWithName:eventName parameters:params];
}
else {
NSLog(#"sharedAnalytics == NO");
}
}
Technically that
sharedAnalytics == nil
evaluates to
sharedAnalytics == NO
which is true and so it reports it as nil.
The problem is this still leaves an NSLog in your production / release code, so maybe you need to do that differently. Again, this is moot relative to your problem, just doing the tests a bit better. Anyway, herewith an idea.
Do the define as follows.
#ifdef DEBUG
#undef sharedAnalytics
#else
#define sharedAnalytics
#endif
To be extremely pure, you should use caps and reduce that to a single branch but lets not go there right now ...
Then when you test do as follows
- (void)logFirebaseEvent:(NSString*)eventName params:(NSDictionary*)params{
#ifdef sharedAnalytics
[FIRAnalytics logEventWithName:eventName parameters:params];
#else
NSLog(#"sharedAnalytics == NO");
#endif
}
While this looks a lot like your original code and does not solve the direct problem you have, it is better since when you compile for DEBUG you get the NSLog bit and when you compile for RELEASE that code is stripped completely from your release version.
What do you get for all this? You should strip all NSLog's from release code as it has performance implications.
Apple library produces extra debug info when compiled with corresponding debug options.
e.g. for sqlite debug info compile with -com.apple.CoreData.SQLDebug to 1 in the Arguments list.
How can we give similar options for our clients when creating a static lib?
I used a DEBUG preprocessor var, but want something more elegant than below,
#ifdef DEBUG
#define DLog(fmt, ...) NSLog((#"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define DLog(...) /* */
#endif
for the above, I have to compile my static lib with DEBUG. But I would like to give my user to set the debug as Apple does.
Question is, how the 'arguments passed on launch' is exposed in the code.
To allow runtime control of the debugging options then you need to allow the user to set a debug option, so provide a function to do that:
YourLibrary.h:
extern void MyLib_SetDebug(BOOL debug);
YourLibrary.m
static BOOL _debug = NO;
void MyLib_SetDebug(BOOL debug)
{
_debug = debug;
}
and create a log function that checks this debug flag and avoids writing if not set:
void DLog(NSString *format, ...)
{
if (!_debug)
return;
va_list va;
va_start(va, format);
NSString *message = [[NSString alloc] initWithFormat:format arguments:va];
va_end(va);
NSLog(#"%#", message);
}
and then delete that DLog macro you are using. You can then compile in Release mode and get debugging behaviour as required at runtime.
When I'm running unit tests, I'd like to skip some code (e.g. I don't want [[UIApplication sharedApplication] openURL:..] to run). I'm looking for a runtime check if I'm currently running units tests or not.
I know I have seen code that checks the Objective-C runtime if unit tests are running but am not able to find it anymore.
You can use this method from google-toolbox-for-mac
// Returns YES if we are currently being unittested.
+ (BOOL)areWeBeingUnitTested {
BOOL answer = NO;
Class testProbeClass;
#if GTM_USING_XCTEST // you may need to change this to reflect which framework are you using
testProbeClass = NSClassFromString(#"XCTestProbe");
#else
testProbeClass = NSClassFromString(#"SenTestProbe");
#endif
if (testProbeClass != Nil) {
// Doing this little dance so we don't actually have to link
// SenTestingKit in
SEL selector = NSSelectorFromString(#"isTesting");
NSMethodSignature *sig = [testProbeClass methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setSelector:selector];
[invocation invokeWithTarget:testProbeClass];
[invocation getReturnValue:&answer];
}
return answer;
}
The reason that NSClassFromString and NSInvocation are used is to allow code compile without linking to xctest or ocunit
Rather that sprinkling "am I testing?" conditionals throughout production code, I isolate the check to one place: main. There, I check for an alternate application delegate for testing. If it's available, I use it instead of the the regular application delegate. This completely bypasses the regular launch sequence:
int main(int argc, char *argv[])
{
#autoreleasepool {
Class appDelegateClass = NSClassFromString(#"TestingAppDelegate");
if (!appDelegateClass)
appDelegateClass = [AppDelegate class];
return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass));
}
}
You can read more about this technique here: How to Easily Switch Your iOS App Delegate for Testing
Select the project, and then the test target:
Select Build Settings and choose All and Combined. Type 'preproc' in the search box - you're after Preprocessor Macros.
Add a macro to the Debug configuration called TEST and set it equal to 1:
Then in your code, you can do this:
#ifndef TEST
[[UIApplication sharedApplication] doEvilThingForTesting];
#endif
Or if you have code that you want to only run in a test environment:
#ifdef TEST
[[UIApplication sharedApplication] doAwesomeTestOnlyThing];
#endif
It's not exactly runtime, but the unit tester compiles the code before it runs the tests IIRC, so it should be the same effect - you're essentially modifying the code right before running the tests.
I'm not sure how long this will continue to work, but it works for me right now with Version 9.0 beta 6 (9M214v).
let isTesting = { () -> Bool in
if let _ = ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] {
return true
} else if let testingEnv = ProcessInfo.processInfo.environment["DYLD_INSERT_LIBRARIES"] {
return testingEnv.contains("libXCTTargetBootstrapInject.dylib")
} else {
return false
}
}()
No build environment or scheme changes are necessary.
It appears that there are two different environment variables in play depending on whether you are running a single test case or the entire test suite. Also, the variable value also differs depending whether or not you are running in simulator or on a real device.
I think you can check like this for Xcode 7.3
-(BOOL) isRunningUnitTests
{
NSDictionary* environment = [ [ NSProcessInfo processInfo ] environment ];
NSString* theTestConfigPath = environment[ #"XCTestConfigurationFilePath" ];
return theTestConfigPath != nil;
}
The easiest (and working in Xcode 7 with XCTest!) way to check is to have a look at the process info for a matching xctest bundle:
static BOOL isRunningTests(void)
{
NSDictionary* environment = [[NSProcessInfo processInfo] environment];
NSString* injectBundle = environment[#"XCInjectBundle"];
return [[injectBundle pathExtension] isEqualToString:#"xctest"];
}
Source: https://www.objc.io/issues/1-view-controllers/testing-view-controllers/#integration-with-xcode
Just use this:
+ (BOOL)isUnitTestRunning
{
Class testProbeClass;
testProbeClass = NSClassFromString(#"XCTestProbe");
return (testProbeClass != nil);
}
I have set PaidApp =1 in my preprocessor macro.
But when i am trying to implement it in my appdelegate file like below:-
- (void) applicationDidFinishLaunching:(UIApplication*)application
{
m_bootup = 1;
m_showhint = 1; // show hand hint on bootload level 1 only
#ifdef PaidApp
[[NSUserDefaults standardUserDefaults] setInteger:1 forKey:#"adDisabled"];
#endif
}
the compiler just skips it... it does not go into the ifdef condition
i am not getting why this is happening
The below link show how i have defined my macro :-
http://i.imgur.com/wA0X2gw.png?1
And this shows how i have used it :-
http://i.imgur.com/9d2PYup.png?1
I'd like to know whether my app is being run on device or simulator at run time. Is there a way to detect this?
Reason being to test bluetooth api with simulator:
http://volcore.limbicsoft.com/2009/09/iphone-os-31-gamekit-pt-1-woooohooo.html
#if TARGET_OS_SIMULATOR
//Simulator
#else
// Device
#endif
Pls refer this previous SO question also What #defines are set up by Xcode when compiling for iPhone
I created a macro in which you can specify which actions you want to perform inside parentheses and these actions will only be performed if the device is being simulated.
#define SIM(x) if ([[[UIDevice currentDevice].model lowercaseString] rangeOfString:#"simulator"].location != NSNotFound){x;}
This is used like this:
SIM(NSLog(#"This will only be logged if the device is simulated"));
TARGET_IPHONE_SIMULATOR is defined on the device (but defined to false). and defined as below
#if TARGET_IPHONE_SIMULATOR
NSString * const DeviceMode = #"Simulator";
#else
NSString * const DeviceMode = #"Device";
#endif
Just use DeviceMode to know between device and simulator
Check if simulator
#if TARGET_IPHONE_SIMULATOR
// Simulator
#endif
Check if device
#if !(TARGET_IPHONE_SIMULATOR)
// Device
#endif
Check for both
#if TARGET_IPHONE_SIMULATOR
// Simulator
#else
// Device
#endif
Please note that you should not ifdef on
TARGET_IPHONE_SIMULATOR because it will always be defined to either 1 or 0.
From XCode 9.3+ , Swift
#if targetEnvironment(simulator)
//Simulator
#else
//Real device
#endif
Helps you to code against device type specific.
You can use the TARGET_IPHONE_SIMULATOR preprocessor macro to distinguish between device and simulator targets.
Use this below code:
#if targetEnvironment(simulator)
// iOS Simulator
#else
// Device
#endif
Works for Swift 4 and Xcode 9.4.1
if anyone is looking for Unity solution i did this, the only way i found how.
using System.Globalization;
public static bool IsArm() {
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(SystemInfo.processorType, "ARM", CompareOptions.IgnoreCase) >= 0;
}