iOS Developer Library

Developer

Start Developing iOS Apps Today

PDF
On This Page

Writing a Custom Class

As you develop iOS apps, you’ll find many occasions when you need to write your own custom classes. Custom classes are useful when you need to package custom behavior together with data. In a custom class, you can define your own behaviors for storing, manipulating, and displaying your data.

For example, consider the World Clock tab in the iOS Clock app. The cells in this table view need to display more content than a standard table view cell. This is a good opportunity to implement a subclass that extends the behavior of UITableViewCell to let you display additional custom data for a given table view cell. If you were designing this custom class, you might add outlets for a label to display the hours ahead information and an image view to display the custom clock on the right of the cell.

image: ../Art/custom_class_2x.png

This chapter teaches you what you need to know about Objective-C syntax and class structure to finish implementing the behavior of your ToDoList app. It discusses the design of ToDoItem, the custom class that will represent a single item on your to-do list. In the third tutorial, you’ll actually implement this class and add it to your app.

Declaring and Implementing a Class

The specification of a class in Objective-C requires two distinct pieces: the interface and the implementation. The interface specifies exactly how a given type of object is intended to be used by other objects. In other words, it defines the public interface between instances of the class and the outside world. The implementation includes the executable code for each method declared in the interface.

An object should be designed to hide the details of its internal implementation. In Objective-C, the interface and implementation are usually placed in separate files so that you need to make only the interface public. As with C code, you define header files and source files to separate public declarations from the implementation details of your code. Interface files have a .h extension, and implementation files have a .m extension. (You’ll actually create these files for the ToDoItem class in Tutorial: Add Data—for now, just follow along as all of the pieces are introduced.)

Interface

The Objective-C syntax used to declare a class interface looks like this:

  1. @interface ToDoItem : NSObject
  2. @end

This example declares a class named ToDoItem, which inherits from NSObject.

The public properties and behavior are defined inside the @interface declaration. In this example, nothing is specified beyond the superclass, so the only behavior expected to be available on instances of ToDoItem is the behavior inherited from NSObject. All objects are expected to have a minimum behavior, so by default, they must inherit from NSObject (or one of its subclasses).

Implementation

The Objective-C syntax used to declare a class implementation looks like this:

  1. #import "ToDoItem.h"
  2. @implementation ToDoItem
  3. @end

If you declare any methods in the class interface, you’ll need to implement them inside this file.

Properties Store an Object’s Data

Consider what information the to-do item needs to hold. You probably need to know its name, when it was created, and whether it’s been completed. In your custom ToDoItem class, you’ll store this information in properties.

Declarations for these properties reside inside the interface file (ToDoItem.h). Here’s what they look like:

  1. @interface ToDoItem : NSObject
  2. @property NSString *itemName;
  3. @property BOOL completed;
  4. @property NSDate *creationDate;
  5. @end

In this example, the ToDoItem class declares three public properties. Unless you specify otherwise, other objects can both read and change the values of the properties.

To declare a property read-only, you specify that in a property attribute. For example, if you don’t want the creation date of ToDoItem to be changeable, you might update the ToDoItem class interface to look like this:

  1. @interface ToDoItem : NSObject
  2. @property NSString *itemName;
  3. @property BOOL completed;
  4. @property (readonly) NSDate *creationDate;
  5. @end

Properties can be private or public. Sometimes it makes sense to make a property private so that other classes can’t see or access it. For example, if you want to keep track of a property that represents the date an item was marked as completed without giving other classes access to this information, you make the property private by putting it in a class extension that you add after the #import statement the top of your implementation file (ToDoItem.m), as shown here.

  1. #import "ToDoItem.h"
  2. @interface ToDoItem ()
  3. @property NSDate *completionDate;
  4. @end
  5. @implementation ToDoItem
  6. @end

You access properties using getters and setters. A getter returns the value of a property, and a setter changes it. A common syntactical shorthand for accessing getters and setters is dot notation. For properties with read and write access, you can use dot notation for both getting and setting a property’s value. If you have an object toDoItem of class ToDoItem, you can do the following:

  1. toDoItem.itemName = @"Buy milk"; //Sets the value of itemName
  2. NSString *selectedItemName = toDoItem.itemName; //Gets the value of itemName

Methods Define an Object’s Behavior

Methods define what an object can do. A method is a piece of code that you define to perform a task or subroutine in a class. Methods have access to data stored in the class and can use that information to perform an operation.

For example, to give a to-do item (ToDoItem) the ability to get marked as complete, you can add a markAsCompleted method to the class interface. Later, you’ll implement this method’s behavior in the class implementation, as described in Implementing Methods.

  1. @interface ToDoItem : NSObject
  2. @property NSString *itemName;
  3. @property BOOL completed;
  4. @property (readonly) NSDate *creationDate;
  5. - (void)markAsCompleted;
  6. @end

The minus sign (-) at the front of the method name indicates that it is an instance method, which can be called on an object of that class. This minus sign differentiates it from class methods, which are denoted with a plus sign (+). Class methods can be called on the class itself. A common example of class methods are class factory methods, which you learned about in Working with Foundation. You can also use class methods to access a piece of shared information associated with the class.

The void keyword is used inside parentheses at the beginning of the declaration to indicate that the method doesn’t return a value. In this case, the markAsCompleted method takes in no parameters. Parameters are discussed in more detail in Method Parameters.

Method Parameters

You declare methods with parameters to pass along a piece of information when you call a method.

For example, you can revise the markAsCompleted method from the previous code snippet to take in a single parameter that determines whether the item is marked completed or uncompleted. This way, you can toggle the completion state of the item instead of setting it only as completed.

  1. @interface ToDoItem : NSObject
  2. @property NSString *itemName;
  3. @property BOOL completed;
  4. @property (readonly) NSDate *creationDate;
  5. - (void)markAsCompleted:(BOOL)isComplete;
  6. @end

Now, your method takes in one parameter, isComplete, which is of type BOOL.

When you refer to a method with a parameter by name, you include the colon as part of the method name, so the name of the updated method is now markAsCompleted:. If a method has multiple parameters, the method name is broken up and interspersed with the parameter names. If you wanted to add another parameter to this method, its declaration would look like this:

  1. - (void)markAsCompleted:(BOOL)isComplete onDate:(NSDate *)date;

Here, the method’s name is written as markAsCompleted:onDate:. The names isComplete and date are used in the implementation to access the values supplied when the method is called, as if these names were variables.

Implementing Methods

Method implementations use braces to contain the relevant code. The name of the method must be identical to its counterpart in the interface file, and the parameter and return types must match exactly.

Here is a simple implementation of the markAsCompleted: method discussed earlier:

  1. @implementation ToDoItem
  2. - (void)markAsCompleted:(BOOL)isComplete {
  3. self.completed = isComplete;
  4. }
  5. @end

Like properties, methods can be private or public. Public methods are declared in the public interface and so can be seen and called by other objects. Their corresponding implementation resides in the implementation file and can’t be seen by other objects. Private methods have only an implementation and are internal to the class, meaning they’re only available to call inside the class implementation. This is a powerful mechanism for adding internal behavior to a class without allowing other objects access to it.

For example, say you want to keep a to-do item’s completionDate updated. If the to-do item gets marked as completed, set completionDate to the current date. If it gets marked as uncompleted, set completionDate to nil, because it hasn’t been completed yet. Because updating the to-do item’s completionDate is a self-contained task, the best practice is to write its own method for it. However, it’s important to make sure that other objects can’t call this method—otherwise, another object could set the to-do item’s completionDate to anything at any time. For this reason, you make this method private.

To accomplish this, you update the implementation of ToDoItem to include the private method setCompletionDate that gets called inside markAsCompleted: to update the to-do item’s completionDate whenever it gets marked as completed or uncompleted. Notice that you’re not adding anything to the interface file, because you don’t want other objects to see this method.

  1. @implementation ToDoItem
  2. - (void)markAsCompleted:(BOOL)isComplete {
  3. self.completed = isComplete;
  4. [self setCompletionDate];
  5. }
  6. - (void)setCompletionDate {
  7. if (self.completed) {
  8. self.completionDate = [NSDate date];
  9. } else {
  10. self.completionDate = nil;
  11. }
  12. }
  13. @end

At this point, you’ve designed a basic representation of a to-do list item using the ToDoItem class. ToDoItem stores information about itself—name, creation date, completion state—in the form of properties, and it defines what it can do—get marked as completed or uncompleted—using a method. Now it’s time to move on to the next tutorial and add this class to your app.