iOS Prefix.pch best practices - ios

I have seen many developers that add various convenience macros to the Prefix.pch of their iOS projects.
What do (or don't) you recommend adding to the iOS Prefix.pch file? What does your Prefix.pch look like?

Ewww… don't put macros in a .pch file! A .pch file is, by definition, a project specific precompiled header. It really shouldn't be used beyond the context of the project and it really shouldn't contain anything but #includes and #imports.
If you have some macros and such that you want to share between headers, then stick 'em in a header file of their own — Common.h or whatever — and #include that at the beginning of the .pch.

For modern iOS and OS X, people should be using Modules. This is enabled by default for new projects, and importing/inclusion is accomplished using #import.
Modules allow the compiler to create an intermediate representation of the contents of a module (e.g. a framework's headers). Much like a PCH, this intermediate representation may be shared across multiple translations. But modules take this one step further because a module is not necessarily target specific, and their declarations need not be localized (to a *.pch). This representation can save you a ton redundant compiler work.
Using modules, you do not need a PCH, and you probably should just do away with them entirely -- in favor of using #import local to the dependency. In that case, a PCH is only saving you from typing inclusions local to dependencies (which IMO you should be doing anyway).
Now, if we look back to the original question: You should avoid filling your PCH with all sorts of random things; Macros, constants, #defines, and all sorts of little libraries. Generally, you should omit what really is unnecessary to the majority of your source files. Putting all sorts of stuff in your PCH is just adding a bunch of weight and dependency. I see people put everything they link and more to in the PCH. In reality, auxiliary frameworks typically only need to be visible to a few translations in most cases. E.g. "Here is our StoreKit stuff - let's import StoreKit only where it must be visible. Specifically, these 3 translations". This keeps your build times down, and helps you keep track of your dependencies, so that you may reuse code more easily. So in an ObjC project, you would usually stop at Foundation. If there is a lot of UI, then you might consider adding UIKit or AppKit to your PCH. This is all assuming you want to optimize build times. One of the problems with large PCHs that include (nearly) everything is that removing unnecessary dependencies is very time consuming. Once your project's dependencies grow and your build times go up, you need to fight back by eliminating unnecessary dependencies in order to reduce your build times. Also, anything that changes often should generally be kept out of your PCH. A change requires a full rebuild. There are some options to share PCHs. If you use PCHs, do aim to support sharing.
As far as what I put in my PCH: I stopped using them for the vast majority of targets years ago. There just usually is not enough in common to qualify. Bear in mind, I write C++, ObjC, ObjC++ and C - the compiler emits one for each lang in your target. So enabling them often resulted in slower compile times and higher I/O. Ultimately, increasing dependency is not a good way to fight dependency in complex projects. Working with multiple languages/dialects, there are is much variation in the dependencies required for a given target. No, I would not advise that as optimal for every project, but that does give some perspective to dependency management in larger projects.
References
http://clang.llvm.org/docs/Modules.html
http://clang.llvm.org/docs/PCHInternals.html
Notes
This question was originally asked a few years before Modules' introduction.
Presently (Xcode 5.0), modules work for C and ObjC, but not C++.

I agree with bbum. My take on the PCH file is that it should contain pretty much only #include or #import statements. So if you have a bunch of helpful, high-level macros, define them in something like Common.h and #import that file, as bbum suggested.
I usually go a step further and use the PCH file to #import a file called XXCategories.h (where XX is the class naming prefix convention you use) that contains #imports for all my UIKit and Foundation class categories: NSString+XXAdditions.h, UIColor+XXAdditons.h, etc.

create an header file "macros.h"
import this header into Prefix.pch
In this macros.h put all the frameworks and other important things.
If you are worried about performance, don't worry, look what apple says:
Headers and Performance
If you are worried that including a master header file may cause your
program to bloat, don’t worry. Because OS X interfaces are implemented
using frameworks, the code for those interfaces resides in a dynamic
shared library and not in your executable. In addition, only the code
used by your program is ever loaded into memory at runtime, so your
in-memory footprint similarly stays small.
As for including a large number of header files during compilation, once again, don’t worry. Xcode provides a precompiled
header facility to speed up compile times. By compiling all the
framework headers at once, there is no need to recompile the headers
unless you add a new framework. In the meantime, you can use any
interface from the included frameworks with little or no performance
penalty.
also in my macros.h I put a lot of constants like:
// delegate
#define UIAppDelegate (AppDelegate *)[[UIApplication sharedApplication] delegate]
#define APPDELEGATE ((AppDelegate *)[[UIApplication sharedApplication] delegate])
// system
#define IS_IPHONE_4INCH (UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPhone && [UIScreen mainScreen].bounds.size.height==568)
#define IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
// screen size
#define IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_IPHONE_4 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 480.0)
#define IS_IPHONE_5 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 568.0)
#define IS_IPHONE_6 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 667.0)
#define IS_IPHONE_6PLUS (IS_IPHONE && [[UIScreen mainScreen] nativeScale] == 3.0f)
#define IS_IPHONE_6_PLUS (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 736.0)
#define IS_RETINA ([[UIScreen mainScreen] scale] == 2.0)
#define IS_RETINA_DISPLAY ([[UIScreen mainScreen] respondsToSelector:#selector(displayLinkWithTarget:selector:)] && ([UIScreen mainScreen].scale == 2.0))
#define IS_PORTRAIT UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])
#define IS_LANDSCAPE UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])
//system version
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
// math
#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
#define RADIANS_TO_DEGREES(radians) ((radians) * (180.0 / M_PI))
// cores
#define RGB(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1]
#define RGBA(r,g,b,a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:a]
#define MAKECOLOR(R, G, B, A) [UIColor colorWithRed:((float)R/255.0f) green:((float)G/255.0f) blue:((float)B/255.0f) alpha:A]
#define MAKECOLORFROMHEX(hexValue) [UIColor colorWithRed: ((float)((hexValue & 0xFF0000) >> 16))/255.0 green:((float)((hexValue & 0xFF00) >> 8))/255.0 blue:((float)(hexValue & 0xFF))/255.0 alpha:1.0]
//customizations
#define SHOW_STATUS_BAR [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
#define HIDE_STATUS_BAR [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
#define SHOW_NAVIGATION_BAR [self.navigationController setNavigationBarHidden:FALSE];
#define HIDE_NAVIGATION_BAR [self.navigationController setNavigationBarHidden:TRUE];
#define VC_OBJ(x) [[x alloc] init]
#define VC_OBJ_WITH_NIB(x) [[x alloc] initWithNibName : (NSString *)CFSTR(#x) bundle : nil]
#define RESIGN_KEYBOARD [[[UIApplication sharedApplication] keyWindow] endEditing:YES];
#define CLEAR_NOTIFICATION_BADGE [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
#define REGISTER_APPLICATION_FOR_NOTIFICATION_SERVICE [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)]
#define HIDE_NETWORK_ACTIVITY_INDICATOR [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
#define SHOW_NETWORK_ACTIVITY_INDICATOR [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];

Related

IOS macros issues

I have a small issues with macros
I have the following macro declared on project-Prefix.pch file
#define IS_IOS7_AND_UP ([[UIDevice currentDevice].systemVersion floatValue] >= 7.0)
and I have a macrofile.h which contain the following code, but when I compile I got error "Invalid token at start of a preprocessor expression"
#if (IS_IOS7_AND_UP>0) //error-> "Invalid token at start of a preprocessor expression"
#define CELL_CONTENT_WIDTH 320.0f
#else
#define CELL_CONTENT_WIDTH 300.0f
#endif
Can you please tell me what's wrong about this, actually I need this to be a macro which prevent me of changing many files.
There is no way to do exactly what you are saying. Instead, try defining CELL_CONTENT_WIDTH like this:
#define CELL_CONTENT_WIDTH (IS_IOS7_AND_UP ? 320.0 : 300.0)
This will do the check at runtime without changing any of your existing code.

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)

Support of several iOS - application development

With the release of iOS6, I am going to update my application using the new feature of iOS6, such as UICollectionView.
When I will deploy it on App Store, my application will be only available for devices on iOS6.
Question: How can I let the devices on iOS 5.1 using the older release which do not use UICollectionView?
I could go more deeper by asking: What is the strategy to upgrade an application without loosing the users which have not yet upgrade their OS?
Thank you for your advice.
You could check for availability of new iOS6 classes with Class tmp = NSClassFromString(#"UIActivityViewController");
if (tmp) {//use iOS6 class}else{//do something else}. Also, you could use some macros, like __IPHONE_5_0, put some code between #ifndef or #if defined(...). Good Luck!
This one will work perfectly:
NSString * systemVersion = [[UIDevice currentDevice] systemVersion];
if ([self.systemVersion compare:#"5.0" options:NSNumericSearch] != NSOrderedAscending)
{
osHigherThaniOS5 = TRUE;
}
if ([self.systemVersion compare:#"6.0" options:NSNumericSearch] != NSOrderedAscending)
{
osHigherThaniOS6 = TRUE;
}
Just determine the system version and set your conditions accordingly :)

#define based upon platform [iPhone or iPad]

I'm trying to make my iPhone app compatible with the iPad.
In a header file I set up some constants.
Because of the larger screen I want some constants used for images to be larger on the iPad than on to the iPhone.
I found some suggestions on the internet to accomplish this:
#if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
#define imgAmcWidth 656.0f
#define imgAmcHeight 36.0f
#else
#define imgAmcWidth 240.0f
#define imgAmcHeight 20.0f
#endif
This seems to satisfy my needs.
Unfortunately xcode 4 fails to compile this giving an error: 'Token "[" is not valid in preprocessor..' [LLVM GCC 4.2].
What am I doing wrong?
While probably not the most elegant solution but to prevent a major rewrite of the code I decided to use the following trick:
#define iPad UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
#define imgAmcWidth (iPad ? 639.0f : 240.0f)
// etc..
UI_USER_INTERFACE_IDIOM and UIUserInterfaceIdiomPad are not preprocessor things. They are part of iOS, so you should do:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
<define your constants here>
} else {
<define your constants here>
}
See also this if you plan to support iOS versions previous to 3.2

Is there a specific Xcode compiler flag that gets set when compiling for iPad?

Is there a specific Xcode compiler flag that gets set when compiling for iPad?
I want to conditionally compile iPad vs iPhone/iPod Touch code for example:
#ifdef TARGET_IPAD
code for iPad
#else
code for iPhone
#endif
I know there is already TARGET_OS_IPHONE and TARGET_CPU_ARM in TargetConditionals.h but anything that easily and specifically targets iPad?
-Rei
The correct API to use for run-time checking of iPad vs. iPhone/iPad Touch is:
BOOL deviceIsPad = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad);
The UIDevice header filer also includes a convenient macro, UI_USER_INTERFACE_IDIOM(), which will be helpful if your deployment target is < iPhone 3.2.
#define UI_USER_INTERFACE_IDIOM() ([[UIDevice currentDevice] respondsToSelector:#selector(userInterfaceIdiom)] ? [[UIDevice currentDevice] userInterfaceIdiom] : UIUserInterfaceIdiomPhone)
So you could just say, negatively:
BOOL deviceIsPad = (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPhone);
Instead of using compile-time flags, you should use run-time check e.g. use NSClassFromString to see if a class exists because the same app can be installed on both devices.
And because of the possibility of running the app in both devices, there isn't a built-in compile-time check whether it targets iPad or not.
Currently I didn’t find anything that would let you check if you are on an iPad, but I’m also not sure if Apple recommends runtime checks. Here’s an excerpt from Apple:
In addition to your view controllers, any classes that are shared between iPhone and iPad devices need to include conditional compilation macros to isolate device-specific code. Although you could also use runtime checks to determine if specific classes or methods were available, doing so would only increase the size of your executable by adding code paths that would not be followed on one device or the other. Letting the compiler remove this code helps keep your code cleaner.
However, there is no place I could find more specific information about conditional compilation macros.
For multiple targets sharing the same project/code, I'm doing this by editing the C flags for my iPad target.
With the [myapp]-iPad target chosen as the active target, pick Project -> Edit Active Target [myapp]-iPad. Search for "c flags" and double-click. Add a flag for "-D TARGET_IPAD". Now the symbol TARGET_IPAD will be defined only for your iPad target.
Of course, this only works if you're using separate targets for iPad and iPhone. If you're running the same binary on both, obviously there's nothing the compiler can do for you. (However, the 3.2 SDK as of the end of January doesn't even support Universal apps yet.)
(Edited; I was confused about the terminology of "Universal" apps etc.)
Or -> just to be sure
-(BOOL)isDeviceAniPad
{
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 30200
BOOL deviceIsPad = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad);
return deviceIsPad;
#endif
return NO;
}
I think this will do
-(BOOL)isDeviceAniPad
{
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 30200
return ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad);
#endif
return NO;
}

Resources