Today I'm going to cover some useful tips and tricks. These are presented in no particular order, and are pretty much unrelated to each other. Hopefully you'll find some, or all of them useful.
1. Regarding the upcoming iPad 2
Reports are starting to surface that the next version of the iPad will support a retina type display. Apple will no doubt repeat what they did with the iPhone 4 and double the resolution (4 times the number of pixels). This makes it easy to support old apps on the new device by employing pixel doubling.
But, you can start preparing for this now! For every iPad image you use, include a higher resolution (double sized) image with the @2x suffix. And for icons, include a 144x144 (double 72x72) icon. I've included a 144x144 icon in all my iPad apps since the iPhone 4 was announced, betting on Apple doubling the iPad resolution. It's cheap to do, and if the predictions are wrong, there's no harm in having an unused icon.
As a sub tip, you should include the following sizes for your icons: 144x144, 114x114, 72x72, 58x58, 57x57, 50x50, 29x29
2. self.var vs var
In your classes, when use the following syntax:
DWClass.h
@interface DWClass : NSObject { NSObject *myObject; } @property (nonatomic, retain) NSObject *myObject;
DWClass.m
@synthesize myObject;
You're telling the compiler that your new class DWClass will have a property called myObject, and that it should create setMyObject (setter) and myObject (getter) methods to access that property. And that those methods should handle your retain/release cycles for you. Any other objects that need to interact with your myObject property, will do so by like this:
dwClass.myObject // (assuming dwClass is an instance of DWClass)
And this will actually call the appropriate setter/getter for the myObject property, which in turn interacts with the myObject instance variable of the dwClass.
Inside the DWClass however, you can access the property like this:
self.myObject
And the same setter/getter methods are used.
Inside the DWClass, you can also access the myObject instance variable directly just by referencing it. DO NOT do this. If you do that, you're not using the setter/getter methods, which means you're not automatically handling the retain/release calls. This is a surefire way to create hard to find bugs in your code. Plus, there are other problems with doing this. If down the road, you need to do something special whenever that property is accessed or updated, so you ditch the @synthesize and create your own setter/getter methods, you're now going to miss even more than the retain/release calls, you're going to miss whatever else you've added.
This entire tip also applies to non-object properties. Even if you're using a standard int as a property, always use the self.variable syntax to access it. It's just good practice and will save you headaches down the road.
3. Keeping your secrets, secret
Often when you're accessing other services, Twitter, Dropbox, your own servers etc, you may need to store passwords, API keys, etc in your code. It's dead easy to just have a constant like this: @"MySuperSecretKey" and be done with it. If you do that though, you may as well post the secret on your web site for all to see. Since, it's trivial for a bad guy to extract your secret from the compiled code they download from the App Store after your release. This is a bad thing. In the case of Twitter for example, some spammer could put your secret key into a rogue app that spam blasts users. Every one of those tweets will say it was sent by your app, and your legit app will be blocked pretty quickly. Your users will be locked out until you submit a fix and have it approved by Apple, say goodbye to two weeks of sales, not to mention, all the bad reviews you'll receive for selling a non-functioning app.
So, keep your secrets secret, use some encryption inside of your app to encrypt your keys etc. It doesn't have to be complex as the bad guy usually just looks for low hanging fruit (unless they are specifically targeting you). You can use one of the many encryption libraries available, or even roll your own if you're so inclined.
4. Ensure the App Store knows you support multiple languages when you do so
This tip comes from a mistake I made in version 1.0 of the Cruze app. The app supported English and French from the get go, but it was done by detecting the language of the user via code and loading the correct set of files for the primary language. This worked great and was way less work than using Apple's recommended method of localization for every nib etc.
One problem though, once the app was released to the store, the only supported language listed in iTunes was English. Because I used my own language detection iTunes Connect didn't detect French. I fixed this in 1.5 by using Apple's localization on a small dummy text file. One that I don't actually use for anything in the app, but that is enough to trigger the language detection tools Apple uses.
Update: see Ovogame's comment below for an even better way to fix this issue.
5. AppName_Prefix.pch
There's a file in your XCode project named AppName_Prefix.pch (where AppName, is your app's name). This file is included at the top of every source file in your project. It's a great place for you to store any constants you need across your app.
6. A Better NSLog()
A common method of debugging is to add NSLog() calls throughout your code. The messages are echoed to the screen as the code runs and you can see what's happening, giving you hints as to what's causing bugs. When you're finished however, and you want to do your final build, all of those NSLog() calls remain in your final build and all of those strings are available for anyone looking through your binaries to see. Who knows what secrets you might disclose.
Instead of using NSLog(), I use DebugLog(). This is a tweak of a function I’ve seen others use, based on the answer here: http://stackoverflow.com/questions/300673/is-it-true-that-one-should-not-use-nslog-on-production-code.
Add this to your AppName_Prefix.pch file:
#ifndef DebugLog #ifdef DEBUG #define DebugLog( s, ... ) NSLog( @"<%p %@:(%d)> %@", self, \ [[NSString stringWithUTF8String:__FUNCTION__] lastPathComponent], __LINE__, \ [NSString stringWithFormat:(s), ##__VA_ARGS__] ) #else #define DebugLog( s, ... ) #endif // DEBUG #endif // DebugLog
Now, replace all of your NSLog() debugging statements with DebugLog(), and define DEBUG in your debug configuration (sub tip 2: go to your Project Info, Debug configuration, search for Preprocessor Macros, add DEBUG).
After this, use DebugLog() all you like, and the strings are skipped over in your Release and Distribution builds. You also have the added bonus of having the function name and line number included with all debug statements now, making it clear what's generating the messages.
7. *.dSYM files
Whenever you build your app, XCode will output the *.app files, as well as a *.dSYM file. For Debug and Release builds, you can just toss/ignore the *.dSYM file. But for your distribution build, that you're going to submit to the App Store, make sure you keep the .dSYM file. You'll need this later, to analyze crash reports. I'll go into more detail on this in a later post, just know you need to keep the files. For the impatient, you can read more on this here: http://www.anoshkin.net/blog/2008/09/09/iphone-crash-logs/
8. Push Notification Certificates
If you have an app that uses push notifications, you need to generate a certificate with Apple, one of the first steps, is to create a Certificate Request file (CertificateSigningRequest.certSigningRequest), that you send to Apple. Keep this file. When your certificate expires, you'll need to request a new one. You can reuse the same Certificate Request file and skip the first few steps.
9. [object release]; object = nil;
With NSObjects, when you're done with them, you call release to reduce the retain count and let the system know you no longer need it. When that retain count hits 0, the system free's the object and releases the memory allocated back for use later.
It is common practice, to set the object to nil after you call release. This prevents a possible crash later, if you attempt to call a method on an object that has been freed. That's because if you try to call a method on a nil object, the system just ignores it, no error, all is good (or is it). If you don't set the object to nil, then it will still point to the memory address that was allocated for that object. There's a chance that object is still there (if something else was using it and increased the retain count for example). In that case, calling a method on that released object, will still work. But if that memory had actually been released calling the method would cause a crash.
This is why developers often set the object to nil, to prevent that crash in the case of a bug. But, this doesn't fix the bug, it just hides it from you. So, I subscribe to the second school of thought you shouldn't set the object to nil. If you have a bug, let the app crash while you're developing and you'll be able to find and fix that bug. When you no longer have any crashes, you'll know you're (closer to being) bug free, and that you haven't just masked your bugs.