Tuesday, October 27, 2009

Accessors and Memory Leaks

As my understanding of Cocoa memory management creeps forward, I occasionally discover new types of memory leaks in my own code. My latest find involves initializing instance variables using accessor methods.

Let's say I have a class MyViewController with a property theImage. In the interface file, I declare the variable and define it as a property:

UIImage *theImage;

...

@property (nonatomic, retain) UIImage *theImage;

In the implementation file, I use the synthesize directive to give me free accessor methods. Because I have the property set to retain the variable's object, I add a release to the dealloc method:

@synthesize theImage;
...

[theImage release];


At some point in one of the methods, I initialize theImage:


[self setTheImage: [[UIImage alloc] initWithContentsOfFile: filepath]];


Looks innocent enough, right? Trouble is, the image created by the alloc will never get deallocated.

The problem arises because each allocation of an object must be balanced by a release of that object. Likewise, each retain of an object must be countered by a release. In this case, a retain is being done when we set the property using setTheImage; that's because the property was declared with the retain option. No problem; that retain is countered by the release in the dealloc method. But the call to alloc isn't countered anywhere, which leaves us a UIImage instance that is never fully released.

There are several solutions:

1. Use temporary variables with alloc. The above code could be rewritten as:


UIImage *tempImage = [[UIImage alloc] initWithContentsOfFile: filepath];

[self setTheImage: tempImage];

[tempImage release];


Using tempImage gives us a way to balance the alloc with a release.

2. Use autorelease. Here's a less verbose way to plug the leak:


[self setTheImage: [[[UIImage alloc] initWithContentsOfFile: filepath]] autorelease];


The downside here is that autorelease relies on Cocoa's automatic memory management capabilities, which introduces a bit of overhead in the form of less efficient memory usage (because objects end up sticking around longer than they need to) and additional processing (because the cleanup process must run periodically when otherwise it wouldn't have to). Exactly how much this overhead ends up mattering is the subject of much debate.

3. Use a convenience method instead of alloc/init. In many cases, convenience methods are available that make your code even more streamlined while still preventing leaks:


[self setTheImage: [UIImage imageWithContentsOfFile: filepath]];


Behind the scenes, convenience methods allocate, initialize, and autorelease your object, so this code is effectively equivalent to the code in the autorelease example.

I have yet to decide my personal position on the use of autorelease, but I'm going to start making sure I use one of these three approaches when initializing properties.

Monday, October 26, 2009

Xcode: EXC_BAD_ACCESS

This Xcode console message is sometimes the only clue as to why an iPhone app just crashed:

Program received signal: “EXC_BAD_ACCESS”.


According to codza, this message indicates that a method on an object that no longer exists has been called. In my limited experience, it means my code has released something it didn't own. For example, I was recently getting this crash because I was releasing a UIImage property in the dealloc method but not taking ownership of the value back when I was setting the property.

The best advice I've seen for debugging this problem involves putting the executable in a mode that keeps around "zombie" copies of deallocated objects, thus allowing the debugger to display more information about what wrong. And it's easy to configure. Truly a huge time saver.

Thursday, October 22, 2009

UIScrollView for Data Entry Pages

My wine app has more fields on the data entry page than will fit on the iPhone's screen, so I decided to put the controls inside a UIScrollView. I had trouble finding good examples of such oversized data entry pages, so I mostly experimented in Interface Builder.

The first step is to increase the size of the main view so that it's large enough to accommodate full page. This can be done just by dragging the standard resize control on the lower right corner of the view window. As noted previously, this can't be done unless all the Simulated User Interface Elements (in the View Attributes) are set to "None".

The next step is to drag in a UIScrollView control from the Library. Make it the same size as the main view. Drag all labels, text fields, etc. to this scroll view; note that the controls appear under Scroll View in the main window when viewed hierarchically.

The next step is to resize the main view and the scroll view to the size of the iPhone screen. First shrink the scroll view to smallish box in the upper left corner of the main view. Then shrink the main window back to standard size; a fast way to do this is to turn back on one or more of the Simulated Interface Elements. Finally, expand the scroll view so that it fills the main view.

There is one more step that I finally figured out after banging my head against the wall for a few hours (I found the lead here). For some reason, the scroll view will not scroll unless it's explicitly told that its content -- the collection of fields and other controls -- is bigger than its visible area. This must be done in code.

First, add a UIScrollView property to the view controller class. As with other view properties, this means 4 lines of code:
1. Adding an instance variable of type UIScrollView to the header:

UIScrollView *scrollView;

2. Adding a property directive for that instance variable to the header:

@property (nonatomic, retain) IBOutlet UIScrollView *scrollView;

3. Adding a synthesize directive for the property to the implementation file:

@synthesize scrollView;

4. Adding a release of the property to the dealloc method's implementation.

[scrollView release];


Next use Interface Builder to map the scroll view property to the actual UIScrollView control. This means right clicking on the File Owner and dragging from the new property's little circle to the scroll view.

Now the scroll view object can be accessed in code by referencing the new property. What's left is to set the scroll view's contentSize property to the full size of the scroll view. I did this from the view controller's viewDidLoad event:

[scrollView setContentSize: CGSizeMake(320, 640)];


You might have to experiment to determine what size is best for your scroll view.



Thursday, October 08, 2009

Objective C "toString"

Through a little digging, I found that Objective C's equivalent to Java's Object.toString method is NSObject's description method. Of course, it can be called on any object that has NSObject as an ancestor, which is typically all objects.

Wednesday, October 07, 2009

Objective C Hello World, Part III

Completed the tutorial, and the app seems to work as expected. I only ran into one inexplicable quirk: when the app runs, the MyViewController view ends up rendering in the simulator at the upper bound of the screen, beneath the phone's status bar. Here's how the view window looks in Interface Builder:


Here's how it looks in the iPhone simulator:


The green band at the bottom is the background of the main view showing through.

The problem appears related to displaying the simulated status bar in IB. When Status Bar (in the View Attributes palette under "Simulated Interface Elements") is set to "Gray", the height (H on the View Size palette) is set to 460, and it's display-only. Changing Status Bar to "None" makes the height property field editable; changing it to 480 makes the view the full height of the screen. This gets rid of the green band, but leaves the edit field right up against the simulator's status bar. The only solution I can find here is to scoot the field down.

This sure seems like a bug with the simulator. Shouldn't the top edge of the view render just below the status bar? I'm betting this is how the app would behave on an actual device.

Tuesday, October 06, 2009

Objective C Hello World, Part II

I finally fixed the problem I was having with my nib file not getting found! Turns out the MyViewController.xib was set as a file type of "sourcecode.xib"; switching it to "file.xib" solved the problem. File type can viewed and updated by right-clicking the MyViewController.xib file anywhere in Xcode and selecting "Get Info".

I figured out this fix by examining the MoveMe sample app project -- which has been running just fine in the simulator -- and looking for differences with my HelloWorld project. I had already tried going through a similar but different HelloWorld tutorial, but ended up with the same error. I had also tried uninstalling and reinstalling the SDK.

One clue there was something wrong with the .xib itself: double-clicking MyViewController.xib in Xcode never opened the file in Interface Builder. Instead, the file's underlying XML would open, complete with color-coding. I could only get it opened in IB by selecting "Open With Finder" from the context menu, or using File > Open in IB. All of my self-made nibs behaved this way.

I have no idea why this happened. Maybe it's a bug in this Snow Leopard build or Xcode (version 3.2, SDK build 10a432)?

One other issue: I was getting a warning when I compiled MyViewController.xib about there being "no rule to process file". The warning went away when I moved the reference to MyViewController.xib out of Targets > HelloWorld > Compile Sources and into Targets > HelloWorld > Copy Bundle Resources. I don't know if this was important.

Friday, October 02, 2009

Objective C Hello World

This is frustrating. I'm carefully following the iPhone HelloWorld tutorial, but I still have run into a problem with the Adding a View Controller step. At the end of the section, I test the application in the simulator and it promptly crashes. Here's the error message in the console (Run > Console):

2009-10-02 14:01:04.641 HelloWorld[5358:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] was unable to load a nib named "MyViewController"'


This appears to be a reference to the following code in HelloWorldAppDelegate.m:

MyViewController *aViewController = [[MyViewController alloc]

initWithNibName:@"MyViewController" bundle:[NSBundle mainBundle]];


The implication is that the nib file MyViewController.xib can't be found. That file is in fact in my project; it's in the Classes folder, where is was automatically created. This must be user error, because I can't find a single other person via Google that's having the same problem. I tried using the source provided in the tutorial, but the application still crashes. Maybe it's something wrong with my environment?

I poked around, and thanks to a tip from a blog and the official documentation, I was able to get the application to work by using nil instead of specifying the nib file name:

MyViewController *aViewController = [[MyViewController alloc]

initWithNibName:nil bundle:[NSBundle mainBundle]];


Supposedly, specifying nil instead of the nib file name tells the view controller class to assume the nib file has the same name as the class, which in this case is "MyViewController". No idea why this should when hardcoding the name doesn't.

But it turns out this didn't fix the problem. I continued to the next section of the tutorial and changed the MyViewController view background color to lavendar, but the background still shows up white when I run the app. On a hunch, I changed the background of the MainWindow view to green. Now the background shows up green. So no view is getting loaded.



Objective C Method Syntax

The syntax for calling an Objective C method is quite novel in my experience with programming languages. It essentially involves mangling the name of the method, inserting argument expressions at placeholders specified in the method definition.

Let's say we're going to declare a method for a class Alien that moves the alien to a new position on the screen. The method declaration might look like this:

-(int) moveToX: (int) newX Y: (int) newY;

The hyphen indicates this is an instance method. The (int) indicates the method returns an integer (maybe some kind of status code indicating whether or not the move was successful). So far, so good. Now for the trick question: what's the name of the method? It's not "moveToX"; it's "moveToX:Y:". Huh?

The method declared above takes two arguments, the values of which can accessed from within the method using variables newX and newY. Both arguments are integers. Objective C provides for the labeling of method arguments, but it does it in a way that mingles what would normally be thought of as the method name ("moveTo") and the label of the first argument ("X"). The labels of subsequent arguments (like "Y") stand alone.

Argument labeling in method declarations is not required. What is required is a word followed by a list of colon-prefixed argument declarations. For example, here's a more minimalist declaration of the same method:

-(int) moveTo: (int) newX : (int) newY;

The two methods are functionally identical, but the official name of the second method is "moveTo::". This style of syntax, while slightly odd to someone coming from a Java or C++ background, is more consistent with the method-name-then-argument-list approach seen in many other languages.

While this second approach may seem less confusing, it is apparently frowned upon. Consider the syntax for calling this second method:

int n = [myAlien moveTo:10 :30];

This code creates integer variable n, calls the moveTo:: method of the myAlien object with the arguments 10 and 30, and places the result of the method call in n. By itself, this line of code doesn't provide many clues about what the values 10 and 30 are expected to represent. Consider instead a call to the moveToX:Y: method:

int n = [myAlien moveToX: 10 Y: 30];

Here, it's much more apparent what's expected of the provided arguments, thus the code is -- at least in principle -- more readable.

It's important to note, however, that Objective C's support for argument labeling ends here. Method calls must use the full method name. This means that, unlike with argument naming support in some other languages, all method argument labels used in the declaration must appear in the method call, and in the correct order.


References: