Tuesday, March 29, 2011

Use the copy attribute with NSString objects

NSStrings should not be retained instead always be sure to copy them so you have two entirely different NSString objects.

What happens if I retain an NSString?

If we retain an NSString value, it can be modified by some other object and the class containing the NSString object will not know.

eg:- NSMutableString * testString = [NSMutableString stringWithString@"Paul"];
        Student * st = [[Student alloc] init];
        st.name = testString;
       [testString setString:@"Jane"];

In the above example, you can see that we created an immutable string and assigned the name "Paul" to it. Then we set this value to the name property of the Student object. Now what we would want is that the student name should remain "Paul" unless changed explicitly. Now observe the next line of code, since the string object we declared was of type NSMutableString we can change the contents of it.

If the Student.name property is marked as retain, then the value of name will be changed to "Jane" even if it is not set explicitly. Whereas if the Student.name property is marked as assign, then the value of name will remain as "Paul" if someone tries to change the value of testString externally.

So to be on the safe side, always mark your NSString objects with the copy property. If the value assigned to it is mutable, then it will get copied otherwise it will be retained. So we will have no issues either way!

Monday, March 28, 2011

Delegates - To assign or to retain?

Well, the right answer is that delegates need to be marked as assign.

What I mean by assign is when you create a property for the delegate it should be as follows:
@property(nonatomic, assign) id delegate;

Why does it matter?

The whole reason behind this is related to retain cycles. The concept is clearly mentioned in the apple docs (see the section 'Retain cycles' in the Apple docs).

There can be two cases here. However, I feel the second case is the one that occurs more frequently:

Case 1: If a classA has retained the delegate object of classB and classB has retained an instance of classA , then the problem here is that neither classA nor classB will ever be released from memory. Because classB's reference count cannot become zero unless classA is released and the classA object won't be released unless classB is deallocated.

Case 2: If "some class" has retained classA. and ClassB (which is the child of class A) has retained classA via a delegate. In this case, class A will not get released even if "some class" releases it because its retain count will not become zero. Since the parent's retain count has not reached zero, its dealloc is never called so it cannot send a release message to the child.

So if we assign the delegate instead of retaining it, there will be no problem of retain cycles.

Points to Note:
1. In this case, if the delegate is called after the object is already deallocated, your application will crash. So if your class is a delegate of some object which is going to be released, make sure you set that delegate to nil before releasing it.

2. Always call respondsToSelector on a delegate method to ensure it has been implemented before you call it.

Friday, March 25, 2011

Demystifying the viewDidLoad and viewDidUnload methods

The viewDidLoad is called after the controller's view is loaded into memory. The viewDidUnload is called when the controller's view is released from memory. Most of us think of these methods as just two calls that are invoked when the view controller is loaded and unloaded. However the important part of their definition is  the "loaded into memory" and "released from memory" parts. This means that these two functions play a vital role with regards to memory.Usually the viewDidLoad and viewDidUnload functions are called just once and that is the normal  behavior we are all familiar with. However, what we are going to discuss here is about the viewDidLoad and viewDidUnload being called the next time.

The viewDidUnload function will be called in low-memory situations. It is here that the view controller needs to release its views and subviews in order to decrease the memory usage of the application. Then later on the viewDidLoad will be called to recreate the views. So we need to write code in viewDidLoad which are necessary to reinitialize our UI.

What do we need to do?
1. Always make sure you set your outlets to nil in the viewDidUnload method.
eg:- self.myOutlet = nil;
In low-memory situations the xib will be released from memory but doing that alone won't reduce the memory consumption if all the outlets are still retained  in our code. So when we set the outlet to nil, we are invoking the setter function of the outlet which automatically releases it before setting nil to it.

2. Always try to write only UI initialisation code in viewDidLoad. If you write code to alloc/init a variable in your viewDidLoad, then when the method is invoked a second time the variable will be alloc/init'd again causing a memory leak. If you really do need to alloc/init your member variable here, do it only if it is not already allocated.

Note 1: Always set your outlets to nil using the 'self.' prefix otherwise you will only be setting nil to the variable thus causing a memory leak.

Note 2: This is applicable only for those outlets which have been retained using the @property(retain) call which is of course the standard method to handle your outlets.

Thursday, March 24, 2011

How to test your print functionality without a printer

AirPrint is the technology that allows you to wirelessly print your photos and documents directly from your iPad to any AirPrint enabled printer.

Sometime back we  were unable to test the print functionality of one of our apps because we didn't have a WiFi printer. We debated quite a bit about buying a WiFi printer. What held us back? Do we really need to invest in a WiFi printer which could be expensive and the purpose of which is just to test a print functionality?

Fortunately, apple has solved this problem for us in a very cost-effective manner.
If you install the iOS 4.2 SDK, you will have access to the print simulator tool. You can use this to easily test your printing functionality.

You will find the Printer Simulator software at Macintosh HD\Developer\Platforms\
iPhoneOS.platform\Developer\Applications. Just copy it to your applications folder and run it. Now all you have to do is open up your app and click the print option. Once the printing is complete, the printout automatically pops up on your screen.

Does it get any easier than that?

Are your Nib objects leaking memory?

Well, here are the 5 steps to correctly manage memory of nib objects.

Step 1: Declare outlets using the declared properties feature in the header file.

@property (nonatomic, retain) IBOutlet UILabel *nameLabel;

Step 2: Set your outlets to nil in viewDidUnload

- (void)viewDidUnload {
     self.nameLabel = nil;
     [super viewDidUnload];
}

Step 3: Release your outlet in the dealloc method and set it to nil

- (void)dealloc {
      // Release outlets and set outlet variables to nil.
     [nameLabel release];
     nameLabel = nil;
     [super dealloc];
}

What you should remember
1. The call to [super dealloc] should be the last line in your dealloc function and the call to [super viewDidUnload] should be the last line in your viewDidUnload function

Wednesday, March 23, 2011

Writing conditional code for different iOS versions

If you are going to use a function that is available only in later iOS versions, you will need to include conditionals. There are two types of conditionals - compile time and runtime conditionals. The purpose of conditionals is two-fold:

1. You need to use compile-time conditionals to prevent compile problems when running in older versions of the simulator.
2. You need the runtime check if you are going to build on a later version of simulator but are going to install on a device with an older version of iOS.

Before I dive into the details, let me give you an instance where this came in handy for me.

I needed to do an animation for resizing a view. I considered using the [UIView beginAnimation] and [UIView commitAnimation] functions. But when I consulted the docs, it said that the use of these functions are not recommended in iOS versions 4.0 and above.

Instead, they recommended the use of block animation functions (eg:- [UIView animateWithDuration]). So I wrote code for animating using the animateWithDuration function. I was able to successfully compile the program because I am using the 4.3 version simulator. The problem occurred when I tried to run it on an iOS 3.2 iPad. The app crashed when the animation call fired. So then I tried using a compilation conditional for the same as follows:

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
      // code with animateWithDuration
#else
     // code with beginAnimation
#endif

I still got the crash because, since I am building on iOS v4.3, the animateWithDuration function still got compiled into the code. So that's when I finally used the run time check macros.

Here are the steps:

Step 1: Define a macro

#define IF_IOS4_OR_GREATER(...) \
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \
{ \
__VA_ARGS__ \
}

I called my macro IF_IOS4_OR_GREATER because it checks if the runtime version is greater than 4.0 but you can call it anything you like.

Step 2: Call the macro where the runtime check is required. Instead of calling the macro directly in the source code, I decided to confine it to a specific function. I decided to call it isCompatibleOSVersion.

+ (BOOL)isCompatibleOSVersion{
    IF_IOS4_OR_GREATER
    (
     return YES;
     )
   
    return NO;
}

Step 3: Next all that is remaining is to do your processing based on this function call.

if ([self isCompatibleOSVersion]){
        // code with animateWithDuration 
}
else{
    // code with beginAnimation
}

Point to note:
If we want to include something only in OS versions prior to a a specific version, then we don't need the conditional compilation (since we still want the code to appear when compiled in a later version). In this case, only a runtime check is required.

Moral of the story:
Use conditionals wisely.