Categories in Objective-C

   Posted by: Jason Shapiro

Typically, when a developer wants to extend or modify the behavior of a class in an Object Oriented language, inheritance is used.  While Objective-C does support inheritance, it also offers an alternative syntax: "Categories."

The easiest way to conceptualize a category is to think of it as a short cut to add extra methods to an existing class.  This is the key difference between inheritance and categories: inheritance allows you to override methods, add new methods, and add new instance variables / properties; a category is more limited: you cannot create new instance variables / properties... only methods are permitted.

Let's compare and contrast the two with a simple code example.  Let's say I want to add a method to the NSArray class that returns all of the text in an array, as uppercase Strings.  I could try extending NSArray:

   1: @interface StringArrayUpperCaser : NSArray
   2: -(NSString *)stringAtIndexToUpperCase:(NSInteger)index;
   3: @end
   1: #import "StringArrayUpperCaser.h"
   2:  
   3: @implementation StringArrayUpperCaser
   4: -(NSString *)stringAtIndexToUpperCase:(NSInteger)index
   5: {
   6:   NSString *theString = [self objectAtIndex:index];
   7:   return [theString uppercaseString];
   8: }

Unfortunately, I'd find out pretty quickly that I'd need to do a bit more work.  There are certain methods that are required to be overridden in all subclasses of NSArray (not to mention other overrides that would be required to make convenience methods such as "arrayWithObjects" to work properly).

Given that all I wanted to do was add one method to NSArray, this is starting to look like a lot more work than I had bargained for.  So let's try an alternative solution: creating a category.  This process is relatively straight forward.  You'll create an interface and implementation, just as you would with a normal class, however you put the name of your category in parenthesis (to the right of the class name you are enhancing):

   1: @interface NSArray (StringToUpperCase)
   2: -(NSString *)stringAtIndexToUpperCase:(NSInteger)index;
   3: @end
   1: #import "NSArray+StringToUpperCase.h"
   2:  
   3: @implementation NSArray (StringToUpperCase)
   4: -(NSString *)stringAtIndexToUpperCase:(NSInteger)index
   5: {
   6:   NSString *theString = [self objectAtIndex:index];
   7:   return [theString uppercaseString];
   8: }
   9: @end

The convention for naming the physical files is typically <Class Name>+<Category Name>.  In this case, we'd name the files NSArray+StringToUpperCase.h and NSArray+StringToUpperCase.m

Now, when I want to use this new method, I import the category and simply call it on a pointer for an NSArray object:

   1: #import "NSArray+StringToUpperCase.h"
   2:  
   3: int main (int argc, const char * argv[])
   4: {
   5:   @autoreleasepool 
   6:   {
   7:     NSArray *myArray = [NSArray arrayWithObjects:@"this", @"is", @"a", @"test.", nil];
   8:     for( NSInteger i = 0; i < [myArray count]; i++ )
   9:     {
  10:       NSLog(@"%@", [myArray stringAtIndexToUpperCase:i]);
  11:     } 
  12:   }
  13:     return 0;
  14: }

results

You'll note that although we named our categories in the interface / implementation ("StringToUpperCase"), we did not have to reference the category name when using the new method.  All we had to do was create an NSArray object and send the new message.

If you happen to see the following warning ("... may not respond to..."), double check to make sure you imported the category's header file.

category-warning

Besides easing sub-classing requirements, Categories can help with organizations: large classes can be separated into different files, based on functionality.  For example, you may have a SystemManager class that does 'read', 'create', and 'manipulate' functionality.  You could have the core methods declared in the SystemManager class, and create categories for each of the separate specific functionalities: SystemManager (read), SystemManager (create), SystemManager (manipulate).  You could also use categories to separate your deprecated methods from the rest of your class: SystemManager (deprecated).

In summary, Categories are a handy way for quickly extending or modifying the behavior of a class, while also offering benefits in terms of logical organization.

Xcode 4: Deploying Your App to Your Own iPhone / iPad / iPod touch

   Posted by: Jason Shapiro

When I first learned how to write an iOS application, I found that the biggest challenge wasn't in learning Objective-C, nor was it in becoming familiar with the Xcode IDE.  Instead, the main difficulty was in getting my app on to my own device!  Unfortunately, many of the books and tutorials assume you'll figure this out on your own... and as I learned, the process was not trivial:

  • I had to create a public/private key pair (via "Keychain"),
  • Generate a certificate request (via "Keychain"),
  • Login to the Provisional Portal at the Apple Developer Site,
  • Create and download a development certificate with the certificate request,
  • Find & register the UDID of my device,
  • Create an "App ID",
  • Create and download a provisional profile which links my developer certificate, UDID, and App ID together,
  • Install the certificate & profile into Xcode...

... all for the simple purpose of testing out my app on my iPhone!

Well, thankfully, Apple has reduced this complexity with the release of Xcode 4.  Here are the steps you need to take, in order to deploy your apps to your own device (for the purpose of developing & testing only... Ad Hoc & iTunes App Store distribution is still a much more involved process):

  1. Register as a Paid Apple Developer.  Only paid accounts can deploy to actual devices.
  2. Open up Xcode 4's Organizer, and select "Provisioning Profiles" in the left side menu:
    left-top-menu
  3. On the bottom of the right side, 'check' the checkbox labeled "Automatic Device Provisioning": 
    provisioningprofiles
  4. Plug your device into your computer.  Select your device in the left side menu, and click the "Use for Development" button: 
    setupfordev
  5. You will be prompted to log in to your Apple Developer account.  Enter your credentials here:
    login
  6. Xcode will likely you tell you that it doesn't recognize the version of iOS.  Allow it to collect the necessary information when prompted: 
    unknownios
  7. After this has finished, you're done!  You should now see your device listed in the top left list of "Schemes" in Xcode: 
    xcode

That's it - no more dealing with keys, certificates, UDIDs, App IDs, etc (at least for testing on your own device).  Also, note that you shouldn't need to repeat these steps for the same device on the same computer.  Once your device has been configured, it should be available for all of your Xcode iOS projects!

Easy Memory Management in Objective-C (iPhone / iPad)

   Posted by: Jason Shapiro

One of the reasons developers try to avoid learning Objective-C is in regard to its memory management (or rather, its lack of automation). 

I'm going to cut right to the chase: in basic, single threaded applications, memory management really isn't all that awful, and with a few simple rules, you can avoid most of the common mistakes made.

Allocating & Deallocating Memory

When an object is created, space needs to be allocated to store it on the heap (an area of memory for objects).  When that object is no longer in use, it should be deallocated from the heap in order to allow other objects to occupy that space.  This act of deallocation runs two great risks:

  • Forgetting to deallocate some memory before the pointer has dropped out of scope causes a "memory leak."  This means that the memory space is still occupied, despite the fact that it is no longer able to be used (or, for that matter, released).
  • Deallocating space before it's finished being used produces bugs which can be very difficult to track down.  If you have multiple pointers which are referencing the same object, you obviously don't want one of the pointers to remove the object while others are still using it! Unfortunately, the moment an exception occurs could be light years away from when the deallocation took place (hence the difficulty in locating the source of the problem).

Retain & Release

Luckily, with Objective-C, allocating is easy (you simply call "alloc" on the class you wish to create an instance of):

   1:  Customer *cust = [[Customer alloc] init];

Allocating not only creates the object and a place for it in memory, it also adds a single "retain count."  This count is used to determine whether or not an object should be deallocated.  As long as an object has a positive "retain count," it will remain on the heap.  Retain counts can be added explicitly by calling "retain" on a pointer.

   1:  [cust retain];

[Detailed rules on when you should add a retain count are discussed below.]

This "retain count" means that you will never have to deallocate an object yourself.  Instead you "release" an object that you've allocated (or became "an owner of" - more on that below), when you are finished. 

   1:  [cust release];

Every time a "release" message is received, the retain count is decremented.  The memory, however, will not be deallocated until the retain count has reached zero.  This helps reduce errors that would occur by accidentally deallocating an object while it is still "on active duty."

The Golden Rule: Symmetry

If you only remember one thing from this article, let it be this: If you are an owner of an object you MUST release the object when you have finished.  Likewise, if you are not an owner of an object, you MUST NOT release the object.  Therefore what you are looking for is symmetry in your code.  Retain -> Release.  That's it.  All of the other rules and use cases derive from this simple concept.  The next natural questions are:

  • What does it mean to be an owner?
  • When do I want to become an owner?
  • How do I become an owner?

What Does It Mean to be an Owner?

You're the owner of an object when your code has added a retain count to the referenced object.  This retain count could have been added directly (by calling "retain" on the pointer):

   1:  [cust retain];

... or indirectly by calling a "creation" function - more on that below.

When Do I Want to Become an Owner?

You are an owner of any object you create, whether you want to be, or not (again, more on that below).  In addition to that scenario, you'll generally want to become an owner when you are keeping the object beyond the scope of a single function (as an instance variable).  If you aren't an owner of an instance variables that points to an object, you run the risk that the object will be deallocated before you are finished using it.  Therefore, it's important to make sure you add a retain count for all instances variables that point to objects (unless you already added one implicitly, by calling a "creation" function). There are other exceptions & less common situations that you may run into as well, but this is the big one to remember.  

How Do I Become an Owner?

As stated above, you can become an owner of an object by calling the retain function.

You also become an owner by calling a "creation" function.  This is a function that not only returns a pointer to an object, it also adds a retain count on your behalf.

You can recognize a "creation" function by its name: it will start with, or simply be, one of these words: "Alloc," "New," "Copy," or "mutableCopy."  Since a "creation" function adds a retain count on your behalf, you are implicitly the owner of that object, and must eventually call "release."

It is generally assumed that if these words do not appear in the name of the function, and you haven't called "retain" on the pointer, you are not an owner of the object.  Therefore you should NOT release the object when you are finished using it.

   1:  NSString *myStr1 = [[NSString alloc] initWithString:@"Intertech" ];
       // An owner of the NSString (alloc) -- you must eventually call "release."
   2:  NSString *myStr2 = [[NSString stringWithString:@"Intertech"]; 
// NOT an owner of the NSString. -- you should NOT call "release."

Autorelease

When a function has a return value of a pointer to an object that it creates, the retain count needs to be handled in a special way.   For example, look at the following code... What's wrong with it?

   1:  -(Customer *) customer
   2:  {
   3:      Customer *cust = [[Customer alloc] init];
   4:      return cust;
   5:  }

We've broken the golden rule!  We've created an object with "alloc" (therefore adding a retain count), but we haven't released it before the "cust" pointer has fallen out of scope!  So how about this example... any better?

   1:  -(Customer *) customer
   2:  {
   3:      Customer *cust = [[Customer alloc] init];
   4:      [cust release];
   5:      return cust;
   6:  }

Well... now we have symmetry (retain -> release), however it's possible that the Customer object will be deallocated before the caller is able to use and/or retain it!

To get around this problem, Objective-C gives us a special kind of release called "Autorelease."  This function provides a deferred release, meaning we have given up ownership, but the object won't be released until a later time.  It does this by adding the release to an "autorelease pool."  You'll notice that this pool is automatically created and "drained" in your main() function by Xcode:

   1:  int main (int argc, const char * argv[])
   2:  {
   3:      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   4:   
   5:  // ...
   6:   
   7:     [pool drain];
   8:     return 0;
   9:  }

Therefore, you typically don't need to manage the pool yourself.  Instead, simply call "autorelease" when you have created an object in a function (unless your function is a "creation" function, which means the name should start with the one of the "creation" names: "Alloc," "New," "Copy," or "mutableCopy."  In that case, the caller would expect you to leave the retain count on their behalf).

   1:  -(Customer *) customer
   2:  {
   3:      Customer *cust = [[Customer alloc] init];
   4:      return [cust autorelease];
   5:  }

 

The Dealloc Function

When your object has been released enough times that the "retain count" has hit zero, the OS will deallocate your object from memory.  However, most objects that you create will be composed of pointers to other objects.  Consider the following interface:

   1:  @interface Customer : NSObject {}
   2:  @property (nonatomic, retain) FullName *custName;
   3:  @property (nonatomic, retain) Address *custAddr;
   4:  @end

When the customer is deallocated, what happens to the FullName and Address objects?  We wouldn't want to have the OS blindly deallocate these two objects, in case they are still in use by other blocks of code.  Instead, we should be given the opportunity to remove our retain count on these properties (if appropriate) before the Customer object is deallocated.  This is done through a special function called "dealloc."  It is your responsibility to create this function and release all retained instance variables (a call to the parent dealloc should be made at the very end of this function):

   1:  -(void) dealloc
   2:  {
   3:      [custName release];
   4:      [custAddr release];
   5:      [super dealloc];
   6:  }

Putting it together

Here is an example that contains several memory management problems.  See if you can find them all:

   1:  #import "FullName.h"
   2:  @interface Customer : NSObject {
   3:      FullName *_custName;
   4:  }
   5:  - (Customer *) customer;
   6:  - (FullName *) custName;
   7:  - (void) setCustName: (FullName *) newValue;
   8:   
   9:  @end
  10:   
  11:  @implementation Customer
  12:   
  13:  - (Customer *) customer
  14:  {
  15:      Customer *cust = [[Customer alloc] init];
  16:      return cust;
  17:  }
  18:   
  19:  - (FullName *) custName
  20:  {
  21:      [_custName retain];
  22:      return _custName;
  23:  }
  24:   
  25:  - (void) setCustName: (FullName *) newValue
  26:  {
  27:      _custName = newValue;
  28:  }
  29:   
  30:  - (void) dealloc
  31:  {
  32:      [super dealloc];
  33:  }
  34:   
  35:  @end

Problem #1: The Customer function on lines 13 - 17 has broken symmetry.  It creates an object with "alloc" but does not "autorelease" it.  Remember, when a function creates and returns an object, it should call autorelease, rather than release (for details, review the "Autorelease" section above).

Corrected:

   1:  - (Customer *) customer
   2:  {
   3:      Customer *cust = [[Customer alloc] init];
   4:      return [cust autorelease];
   5:  }

Problem #2: The custName function on lines 19 - 23 adds a retain count, but doesn't release it.  In fact, there is no real reason to add a retain count here, since we haven't created a method that uses one of the four "creation" names ("add," "new," "copy," and "mutableCopy"). 

Corrected:

   1:  - (FullName *) custName
   2:  {
   3:      return _custName;
   4:  }

Problem #3:  The setCustName: function on lines 25 - 28 are storing an instance variable, but are not retaining the reference.  Without a retain, it is possible the object will be deallocated before we are finished using it.  In this case, we need to release the old value and retain the new value.  Don't worry about whether or not there is already an old value set for this instance variable.  Unlike other languages (such as the NullPointerException in Java), Objective-C will quietly ignore the "release" call if nothing is there.

Corrected:

   1:  - (void) setCustName: (FullName *) newValue
   2:  {
   3:      [_custName release];
   4:      _custName = [newValue retain];
   5:  }

Problem #4:  We forgot to release our instance variable in the dealloc method!

Corrected:

   1:  - (void) dealloc
   2:  {
   3:      [_custName release];
   4:      [super dealloc];
   5:  }

P.S. - Use Your Tools!

While following the rules described above will drastically reduce the amount of memory issues you experience in your code, you'll probably still miss a few due to exceptional situations and common mistakes.  Thankfully, the iOS SDK includes several tools to help you identify the over and under releasing of objects.  In Xcode you can use the "Analyze" build option to see if it detects any memory errors.  Also, Instruments provides a "Leaks" template (identifies under releasing) and a "Zombies" template (identifies over releasing).  I'll explore these tools in a future post!

Intertech's Mobile DevCon - iOS Follow-up

   Posted by: Jason Shapiro

I want to thank everyone for attending yesterday's Mobile DevCon!  It was really great to meet so many of you and to learn about the different types of mobile projects you are / will be working on.

There are two points that I want to follow-up on from my iOS seminar.  The first is regarding the free developer account from Apple.  I had stated that if you sign up with Apple, you'll be able to download Xcode for free.  During the break, an attendee mentioned that he wasn't able to download Xcode 4 (though Xcode 3 was available).  Instead, there were two options: upgrade to a paid account or pay for Xcode 4 via the Mac App Store.  Another person who heard our conversation told me that he had a free account and was able to download Xcode 4.  Looking at various blogs online, I saw the same "you can't get this" / "yes you can" debates.

So, this morning I followed up with Apple to find out the specifics: With the free account you are able to download Xcode, but only "GA" versions.  Xcode 4 is still considered "beta," so you do need to have a paid account to download the latest version for free.  Another option is to purchase the software from the Mac App Store: http://itunes.apple.com/us/app/xcode/id422352214?mt=12  There is no ETA on when Xcode 4 will be considered "GA."

The second point is a correction I want to make in regard to the iOS Simulator.  I had a minor brain freeze when a question came in as I was configuring the code demo:  "Is there a simulator for the iPad?"  For whatever reason, my brain took that as "Is there another simulator for the iPod (touch)", so I said: "No."  The correct answer, of course, is that there is a simulator for the iPad.  You can access this through the schemes:

iOSsimulator1

or through the simulator itself (Device -> iPad):

iOSsimulator2

Don't forget that you can download all of the Mobile DevCon slides at: http://bit.ly/mobiledevcon and my sample iOS code project at http://bit.ly/mobiledevconioscode - (be sure to read the "ReadMe.rtf" file for instructions on adding GData to the project!)

Find Us
Contact Us 651-288-7000 1-800-866-9884
Home | Training | Curriculum | Course Finder | Schedule | Enroll | Twin Cities Java User Group | Consulting | Foundation | Jobs | About Us | Our Story | Press Room | Instructors | President | Map & Directions | Sitemap

Java Training | JSF / Struts / Spring / Hibernate Training | Java Power Tools Training | .NET 4.0 & Visual Studio 2010 Training | Microsoft Web Development Training | Prism / MVVM / MEF Training | .NET 3.5 and Visual Studio 2008 Training | .NET 2.0 and Visual Studio 2003 Training | Cloud Computing Training | Ajax / Web Services / XML Training | Groovy and Grails Training | SQL Server 2012 Training | SQL Server 2008 Training | SQL Server 2005 Training | Mobile Development Training | SharePoint 2010 Training | SharePoint 2007 Training | Agile, Process, Analysis & Design Training | Arch/Design Patterns Training | Microsoft Official Curriculum Training | Web Development Training | Ruby Training | Rational Application Developer (RAD) Training | WebSphere Application Server Training | WebSphere Portal Training | WebLogic Training | Boot Camp Training | Project Management Training | C / C++ Training | Metro / WinRT / Windows 8 Development Training | Retired

Intertech delivers training on-site and virtually serving cities including Phoenix, AZ | San Francisco, CA | Los Angeles, CA | San Diego, CA | San Jose, CA | Washington, DC | Chicago, IL | Orlando, FL | Boston, MA | Duluth, MN | Minneapolis St. Paul, MN | Rochester, MN | Raleigh-Durham, NC | New York, NY | Philadelphia, PA | Austin, TX | Dallas, TX | Houston, TX | Seattle, WA.