I want to have some "god mode" boolean toggles to quickly change functionality inside my app.
The previous developer created #define macros for these in a file that's included (indirectly) in every other file, but there are some disadvantages:
Every change has to be commited in git
Every change forces a build of the entire project
Is an xconfig file suited for this case? I accomplished what I want by:
Creating an .xconfig file
Adding it to the project
Adding HACKS_TEST = YES to it
Adding preprocessor macro HACKS_TEST=${HACKS_TEST} to the project target
Adding static BOOL const IOHacksTest = HACKS_TEST
and upon logging it, the value is printed correctly. But, if I change the HACK_TEST's value in the .xconfig file, it still builds the entire project. Also, adding one 'hack', needs modifications in the .xconfig file, project target's preprocessor macro section and project's global file.
Is it possible to achieve this without the need of an entire project build?
Is there another solution that doesn't require these many modifications on new 'hacks' adding?
Is there another solution that is more appropriated to my needs and/or easier/better?
What is the best way to have them set to NO for the Release configuration?
Edit:
Along the selected solution, I also added this in the Globals.m file:
if #debug
BOOL IOHacksTest = YES;
#else
BOOL IOHacksTest = NO;
#endif
so for Release builds, all the "hacks" are turned off automatically.
Every change has to be commited in git
You can't, or really shouldn't try to, avoid this. If there's some setting that can affect how the whole project works, it should be archived with the project.
As you've discovered, putting the values in an .xcconfig file doesn't have much advantage over having #defines for everything - if you change the xcconfig file, yo have to rebuild everything.
To avoid that, you'll want to split the declaration and definition of the variables between a header and a source file. So, you'd have a goobals.h file that contains "extern" declarations, like this:
extern BOOL IOHacksTest;
And a goobals.m file that has:
BOOL IOHacksTest = YES;
Then you include goobals.h wherever you need the declarations. If you need to change a value, you only need to recompile a single file.
1, Is it possible to achieve this without the need of an entire
project build?
If you want code to be compiled differently, based on those changing values, then there is no way to do it. In fact, you want the project to be recompiled, because the generated code needs to change.
The best way to prevent an entire project build is to put those values into a header file, and selectively include that header file only in files that need to know about the values that could change.
Is there another solution that doesn't require these many modifications on new 'hacks' adding?
There is no way getting around it if you want compile-time detection. If you change the values, then the compiler has to generate new code. How could the compiler generate the right code if it does not know about the custom changes you want to make with your "changes?"
Is there another solution that is more appropriated to my needs and/or easier/better?
That depends on your actual needs, which you didn't state in the original question. If you must have compile-time knowledge of the changes, you can use the header files, or you can add values to .xcconfig files, or just set them in the project. However, you will still have to recompile all the code that is impacted every time the values change.
If you want the settings to be changed at run time, then this is precisely what NSUserDefaults is designed for. Set an initial value in the source code, or in the Info.plist file, and set it to user-defaults on the app's first launch. Thereafter, you can manage the values from user-defaults.
What is the best way to have them set to NO for the Release configuration?
static BOOL builtInDebugMode = !!(DEBUG);
EDIT
Also, what does !!(DEBUG) do? – Iulian Onofrei
That's a logical-not operator, employed twice. It ensures that its operand will always be either 1 or 0. It was there to guide you to how it could be done (i.e., consider DEBUG or NDEBUG at compile time).
By default, your xcode configuration will have DEBUG=1 for debug builds. You could set it to DEBUG=0 for release builds, and use the above code.
As long as your code properly handles DEBUG, this should be fine. Unfortunately, some code incorrectly uses #ifdef DEBUG rather than #if DEBUG which could cause issues.
Thus, you may be better off with something like...
#if DEBUG
static BOOL builtInDebugMode = YES;
#else
static BOOL builtInDebugMode = NO;
#endif
When compiled in debug mode, DEBUG will be defined as 1. When not in debug mode, it will not be defined at all.
Or, you could add a definition to your project file DEBUG_VALUE
You can set some value in NSUserDefault, is very easy to use, I think.
You can add buttons to your app's UI to toggle these values, or you can let app query some website for this value at first launch, depends your needs.
NSUserDefault is fast.
And you can query at launch, and use it util app quit.
Related
In my app I have an option defined in the Settings.bundle that is used only for debugging purposes.
Is there a way of hiding this from code, for when the application is in production, without manually deleting the option from Settings.bundle/Root.plist ?
Something in the lines of:
#ifdef DEBUG
// Remove the option from the settings bundle
#else
// Show the option in (or don't actually remove the option from) the settings bundle
#endif
Thank you!
There're a few options:
use a separate dev target with special Settings.bundle,
modify Settings.bundle with a Run script at Build Phases for the single target adding the section for DEBUG configuration, check CONFIGURATION variable for that.
I'm almost certain there is no way to do that.
We're using the new Urban Airship iOS plugin for PhoneGap.
In the plugin's plist file, we're supposed to enter the app-specific keys needed to enable push notifications.
The problem is we have two versions, free and paid, of the same app, but the plist file only accommodates one version.
Essentially, we need to modify the Objective-C code to read different plist values, depending on whether it's the free or premium version.
We currently manage both versions with the same code base and Xcode project. Unless we change the plugin code, it seems like we need to create a new Xcode project, which we don't want to do.
How do we adjust Urban Airship's Objective-C files to read different values from the plsit file?
Sorry to keep you waiting, I wanted to give you a very detailed answer instead of rushing last night :) So here we go.
First in your project we need to add a new target. Go to your project settings and right click your target. Click duplicate.
You'll get a new target probably named Target-copy. You'll also get a new info.plist file just for that target.
Next we're going to edit our Pro version's Built Settings. Scroll or search and find Apple LLVM compiler 4.0 Preprocessing. Add to both your Debug and Release configurations. I normally just go with the simple PRO=1. You also need to add PRO=0 to your lite version or it will be undefined when you try to build that version.
Now lets look at how to add a custom plist like I'm sure you'll need. First create two folders. Its important these are folders not groups. In each folder we can create a plist with the exact same filename.
Since Now you can add something to each of them. I just added a key property and a value pro string / lite string. Finally to the code. In the sample project I made I simple overrode viewDidLoad but obviously this will work anywhere. Since the plists have the same name you can load them with one line of code. They'll never get mixed up because they are only copied to their respective target. If you need to do code level based logic you can use the PRO preprocessor we made.
- (void)viewDidLoad
{
[super viewDidLoad];
// This will load the proper plist automatically.
NSLog(#"Plist Value: %#",[[NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Property List" ofType:#"plist"]] objectForKey:#"property"]);
// Also remember we set up a preprocessor PRO. you can use it as well.
if (PRO) {
NSLog(#"Only Show for Pro");
} else {
NSLog(#"Only Show for Lite");
}
NSLog(#"This will show for both");
}
This is the method I use for all my lite/pro version apps so I can share a common codebase without copying it between projects or other complicated systems. It has worked pretty well for me so far :) Happy Coding!
Source
Figured someone may be able to use the project to look at so here it is on GitHub.
Is there anyway to delete the NSLog lines from the app by any trick/tool? I usually use NSLog's in each and every method to understand the flow of control and to know about the values of the app's variables. I also use lots of comment lines to explain the nature of methods and variables.
At some point these NSLogs and comment lines make the program hard to for me to understand. So I need to keep deleting and recreating them. Is there a way to show/hide them by any trick in Xcode?
Use the global research & replace tool (cmd-shift-f, or Edit, find, Find in Workspace)
Clic on Find, select Replace
Style => Regular expression
For the NSLogs, search
NSLog\(.*\).*$
and replace by a space.
For the comments, search
\/\/.*$
and
\/\*.*\*\/
and replace by a space.
And finish by replacing manually those ones
/* fjeizghrij
eopgfjeipgez
*/
because I don't know how to grab them safely?
EDIT :
At last, beware of this global replace because you won't be able to undo ! Copy/paste your project before, for example. You should use the preview fonctionality of the global replace too, and check each entry.
I'm not sure what the exact reason is why you want to remove the NSLog lines and comments.
if you can read the source code hard, to remove the comments, set the comments colour in the Xcode preferences to same as the background or set their font size to 1 and you won't see them when you read the code. :)
I have no idea for the NSLog, but I'm using the following way to avoid the unwanted logging in the final release.
this is a simple macro:
#ifdef DEBUG
#define DebugLog(...) NSLog(__VA_ARGS__)
#else
#define DebugLog(...) { }
#endif
I'm using the DebugLog(...) as I would use the NSLog(...) normally, and the Xcode is logging only in DEBUG mode, I don't need to remove any log when I create the release version of the app.
I'm using a static lib thats giving me a warning when uploading my binary for review by apple.
The method in the static lib that causes the warning(non-public selectors) is never called by me, its corresponding .h is deleted from my proj, but warning still persists.
Given that I know the method name causing the problem, is there a way for me to open/edit this .a and comment/delete the offending piece of code and then use the modified .a in my project.
I don't have access to the .a source to recompile it, and its very old and the creator of it has no contact details for me to track down.
Many Thanks,
-Cake
Quick and dirty solution: Open the .a file in a hex editor and change all instances of the name. Leave the function name the same length so that offsets in the file don't change, just change a letter or something like that. I did a quick test, adding a dummy function to a subproject we're building as a static library then tweaking the function name in the .a file (there were five instances, for what that's worth) and everything built okay. I don't see any reason it wouldn't pass the App Store check after that.
I'm really surprised the function was still there in the final build, though—I thought Dead Code Stripping was supposed to clean out any unused code. Huh.
http://opensource.apple.com/source/cctools/cctools-809/
I don't presume to get your bounty, because I haven't provided an easy solution. But yes, it in theory is possible. You have your work cut out for you.
There are several solutions, depending on your lib and project.
In your build settings :
Enable "dead code stripping" if possible : If the method is never used (even internally), the symbol will be deleted.
Use "Unexported symbol file" : Simply add the symbol into a file and it will be removed from the binary. This will work even if the symbol is used internally.
Enable "Deployment Postprocessing" and "Strip Linked Product" with "Strip Style" set to "All symbol"
(Not sure) Use "Symbols Hidden by Default". This is related to the code generation and should not affect linking, but just in case everything above failed...
No need to hack the binary files. Just turn off the compiler's "unused selectors" warning: -fno-unused-selectors.
If I added a user-defined setting in my build configuration, how can I read that setting in my Objective-C code?
I have two files in my project, debug.plist and release.plist. I want my MainApp.m file to read one of these files based on which build configuration is running. I set up a user-defined setting named "filename" in both the Debug and Release configurations to point to the appropriate file. But I don't know how my MainApp.m file can read the filename variable from the current running configuration.
Here's what I did, I'm not 100% sure if this is what you're after:
Go into the build Settings panel and choose the gear icon in the bottom left: add User-Defined Setting
Create your user defined setting, for example:
MY_LANG -> en_us
Then, in the Preprocessor Macro's setting, you can reference that value:
LANGCODE="$(MY_LANG)"
Now you can refer to LANGCODE in all your source files, and it will be whatever you filled out in your custom build setting. I realize that there's a level of indirection here, but that is intentional in my case: my XCode project contains a bunch of different targets/configurations with their own preprocessor macro's. I don't want to have to go into all of those, just to change the language code. In fact, I define the language code on the project level. I also use MY_LANG in a couple scripts, so just a preprocessor macro wouldn't do. There may be a smarter way, but this works for me.
You can access your user-defined build setting at run-time (as suggested in a comment by #JWWalker)
Add an entry to your Info.plist file, and set it to your User-defined Build Setting
MySetting -> ${MYSETTING}
Read its value from code
Objective-C
[[NSBundle mainBundle] objectForInfoDictionaryKey:#"MySetting"];
[Edit] Swift
guard let mySetting =
Bundle.main.object(forInfoDictionaryKey: "MySetting") as? String
else { print("MySetting not found") }
Swift 4
Lets say "filename" is the String you need in your app.
Add filename=YOUR_STRING to user-defined setting(for debug and release).
And add filename = $(filename) to info.plist.
Then in Swift code:
if let filename = Bundle.main.infoDictionary?["filename"] as? String {
// do stuff with filename
}
else {
// filename wasn't able to be casted to String
}
Your code can't read arbitrary build settings. You need to use preprocessor macros.
EDIT: For example, in the target settings for the Debug configuration, you could add DEBUGGING=1 in the Preprocessor Macros build setting, and not define DEBUGGING in the Release configuration. Then in your source code you could do things like:
#if DEBUGGING
use this file
#else
use the other one
#endif
I tried zmippie suggestion but it didn't work for me.
I got it working with this:
${MY_LANG}
In case anyone else is still stuck looking for how to do preprocessor macros, look for the Apple LLVM - Preprocessing section in Build Settings. Under it, you will see a section called Preprocessor Macros.
This is where by default, Xcode inserts the DEBUG=1 macro for the debug build configuration.
You can add your own here, and give them different values for debug, release and any custom build configs you may have.
To add one, double-click on the current value list for the config you want, and it'll display a nice little editor with one macro on each line. Just add your own macro name, and give it a value the same way the DEBUG one is done.
These can be checked during the preprocessor build phase using #if, #ifdef etc. to provide conditional code or values.
Hope that helps.