What You’ll Learn in This Hour
Understanding Objective-C and its objectives
Using object-oriented principles and good programming style
How inheritance works in the Objective-C world
Object-oriented programming is probably the most commonly used programming paradigm today, but there is no clear definition of what it is. The most common informal description (not definition) is that it is a style of programming in which the basic building blocks are objects—combinations of methods and variables that represent concepts or physical objects in a program. The ability to build a program in which the program components map directly to objects and concepts in the physical world can make it easier for people to develop software because on one level they are dealing with the objects and concepts about which they are trying to build software.
Beyond this basic concept of objects, a variety of other concepts can come into play. These concepts include data abstraction, encapsulation, messaging, modularity, polymorphism, and inheritance. (That list is from the Wikipedia article on object-oriented programming.) Objective-C incorporates all of those, and in this hour you find out more about data abstraction, encapsulation, and modularity. The other concepts are introduced in subsequent hours.
Although this book focuses on Objective-C and does not compare it with other languages, it is important to point out that in the 1960s when object-oriented programming became popular, there was disagreement over how to go about developing actual programs. There were two schools of thought:
Object-oriented languages—Over the years, a number of languages have been built specifically to work with object-oriented programming. These included Simula, Ruby, Eiffel, and Smalltalk.
Object-oriented additions—Partly because of the difficulty of retraining programmers on a totally new language, many hybrid languages have been developed. These include C++ and C# as well Objective-C. Object-oriented features have been added to languages such as PHP and even FORTRAN and COBOL.
When you have an object-oriented language, you can start to build object-oriented projects, but that is just the starting point. There are several ways to proceed, but because Objective-C is used almost exclusively for building OS X and iOS projects, the focus in this section is on them.
One of the major objectives of object-oriented programming is making it easier to reuse existing code. When you encapsulate code into formal objects with well-structured interfaces, you should be able to reuse that code, and your investment in it can pay off in a variety of later uses.
Code reuse is a critical component of Apple’s development environments. Object-oriented programming and its implementation in Objective-C provide the basis of reuse. The structure of reuse on iOS and OS X relies on frameworks. Frameworks are sets of related classes (and a few nonclass entities) that implement functional areas of interest to developers. Frameworks are essentially organizational tools for the classes they contain. Your involvement with frameworks consists of importing the ones necessary to your project; you actually interact with, use, and subclass the classes within a framework. (It is also possible to create your own frameworks, but that is beyond the scope of this book. You can find documentation of the process on developer.apple.com.)
The set of frameworks that you deal with forms either Cocoa or Cocoa Touch. Each has two aspects: one for runtime and one for development.
Tip
In this book, if no distinction is made regarding the environment, then you can assume the reference is to both environments.
When you create Cocoa projects, you commonly start from a template in Xcode. (You also may start from an example on developer.apple.com.) In both cases, the import directives for the appropriate frameworks are already provided, but you can also add any necessary import directives.
You will most commonly deal with two sets of frameworks. As noted previously, these are primarily organizational structures; you find references to them in the documentation you see in Xcode’s Organizer and elsewhere. On OS X, the various frameworks themselves are organized into five layers:
User Experience
Application Frameworks
Graphics and Media (Application Services)
Core Services
Underpinning the whole structure is the Darwin kernel
On iOS, there are five layers:
Application
Cocoa Touch
Media
Core Services
Core OS
As is common in layered architectures, each layer can refer to layers below it but cannot refer to layers above it. Thus, frameworks in Core Services can rely on classes in Core OS (iOS) and Darwin (OS X), but not on anything in the higher layers, such as media and interface-handling classes.
Within this structure, there are two frameworks on which your projects almost always rely. On OS X, they are
AppKit in Application Frameworks
Core Foundation in Core Services
On iOS, they are
UIKit in Cocoa Touch
Foundation in Core Services
Although the frameworks are collections of classes for the most part, they also contain some other entities. In particular, Core Foundation and Foundation contain C elements such as structs and typedefs. This ability to use native C in an Objective-C project is important to this structure.
Objective-C started from C, “the language of the UNIX operating system,” as Brian W. Kernighan and Dennis M. Ritchie put it in the introduction to their classic book, The C Programming Language. C itself is a relatively terse language (the preface to the first edition refers to its “economy of expression”). C played a critical role in the development of object-oriented languages around 1980: Both C++ and Objective-C started as C programs to convert their native syntax to compilable C code. C++ was heavily influenced by Simula, whereas Objective-C’s main influence was Smalltalk. The relative merits of the two object-oriented offspring of C have been hotly debated over the years.
Today, Objective-C is perhaps most widely known as the language of OS X and iOS. Comparisons to other languages are largely irrelevant. What does matter is the basic C syntax that is outlined for you in Appendix A, “C Syntax Summary,” if you need a refresher.
Just as with a natural language, if you start thinking in a language you know well and then mentally translate it into the new language, it takes you much longer to learn the new language. Much of what you know about computer languages in general still applies in Objective-C, and the basics of object-oriented programming apply as well. For many people, the biggest difference between Objective-C and languages such as C itself or C++ is that Objective-C looks different. The next section introduces you to the heart of this difference: methods and messaging.
Tip
Don’t translate the concepts and syntax of the following sections. Use what natural language instructors call total immersion: Jump right in and start writing Objective-C.
Data abstraction is one of the key principles behind Objective-C. Abstraction is not simply a computer term; it is widely used in other areas. It is the process of considering a number of ideas or objects together and leaving out the differences among them so that what remains is the abstraction that is common to all of them. It can be an exercise in logic or philosophy or a children’s game such as, “What do these things have in common?” If the things are a monkey, a goldfish, and a person, the answer might be that they are all animals. That is one abstraction of those objects.
A key part of abstraction is hiding details that are irrelevant to the abstraction so that the abstracted information is more recognizable. That aspect of data abstraction is critical in Objective-C. In the language, its use, and its best practices, the hiding of object details makes the abstractions usable. Hiding is very important in Objective-C.
When you have abstract objects with their differences hidden, you can write code to manipulate the objects. With the differentiations hidden, your code can safely ignore the differences. It is important to learn and remember this principle of hiding differentiations from the abstract objects. Many people mistakenly start out with Objective-C (and other languages, but the focus here is on Objective-C) by dealing with the differentiations that should be hidden.
Thus, if you create a bank account class, to the outside world it should look and function the same way whether it is a savings account or a checking account. The class probably can report its current balance and perhaps pending transactions. Subclasses might be able to report attributes specific to savings or checking accounts, but the abstract bank account class probably cannot do that because reporting on checks that have not yet cleared is not something that a savings account can do (unless it is a hybrid account where you can write checks against the savings account).
As is the case in many programming languages, you can define entities in your code. You can write functions and methods in many languages, and you can declare classes in many object-oriented languages. However, having written a function or method means very little unless you actually call it. By the same token, declaring a class has no meaning unless you actually create an instance of the class.
Encapsulation is related to data abstraction, and it can be demonstrated by writing some pseudocode for a bank account class, as shown in Listing 2.1.
Although this is pseudocode, it begins to introduce a few Objective-C syntax elements that are described in detail in Part II, “Working with the Objective-C Basics.”
#import "BankAccount.h"
@implementation BankAccount
#define checking 1
#define saving 2
#define investment 3
– (float)balance: (int) accountType
{
switch (accountType) {
case checking:
//set returnValue ...
break;
case saving:
//set returnValue ...
break;
case investment:
//set returnValue ...
break;
default:
//set returnValue ... (maybe to nil)
break;
}
return returnValue;
}
@end
This is a method of a bank account class that returns the balance for that account. You pass in the account type, and depending on the account type, different routines fire to return the necessary value.
The structure shown here demonstrates the principle of encapsulation by breaking it. The bank account class does abstract the particularities of each type of account. You call this method and pass in whatever type of account you are interested in, so you might think that you have abstracted the class data.
But the notion of encapsulation added to data abstraction suggests that the abstract object should be self-sufficient. If you have an instance of a bank account class, you should be able to ask it for its balance, and, without you passing in any additional information, the self-contained (encapsulated) class can do whatever is necessary.
This approach means that within the bank account class, you need to store the account type.
Listing 2.2 shows pseudocode for a bank account class header.
@interface BankAccount : NSObject
{
@private int accountType; //variable declaration
}
– (float *)balance; //method declaration
@end
Because the account type is stored within the object, the method to return the bank account’s balance already has that information. The code shown previously in Listing 2.1 can be converted to use that internal value, as shown in Listing 2.3.
#import "BankAccount.h"
@implementation BankAccount
#define checking 1
#define saving 2
#define investment 3
– (float *)balance
{
switch (accountType) {
case checking:
//set returnValue ...
break;
case saving:
//set returnValue ...
break;
case investment:
//set returnValue ...
break;
default:
//set returnValue ...
break;
}
return returnValue;
}
@end
In Listing 2.2, the header of this class, you see that the account type variable is declared as private, which means that it is accessible only from within the class. The only part of the class that is visible to the outside world is the method that returns the balance. It does that based on its own internal data (the account type, and, presumably, transaction data). This bank account class is both abstracted and encapsulated. From the outside, all it can do is return its balance based on its internal values. Anyone accessing this class doesn’t need to know how it does what it does.
Note that in Listing 2.3 everything about the internal workings of the class is hidden. The values of the different types of bank accounts are not exposed. This type of structure might be useful to protect your code from external changes. When you create a bank account instance, you might set its account type by passing in a parameter that might be a string such as “Super Saver Account.” The translation of that string, which is controlled by the marketing area of the bank, to an internal type of account can happen when the specific bank account instance is created; from then on, the terminology of the marketing area is independent of the internal functions of the bank accounts. Likewise, you can change the functionality of an account type without changing anything that is exposed to the outside world. (This happens in banking when regulations change across a banking system, forcing new types of calculations.)
GO TO Hour 8, “Declaring Instance Variables in an Interface File,” p. 111, for more about setting the scope of instance variables, such as making them private, public, or protected.
An important tool for managing encapsulation is the use of accessors, which are small methods that let you access the instance variables of an object without referring to them directly. Accessors sometimes simply retrieve or set the value of a variable, but they also may perform transformations and modifications. For example, in a number of framework accessor methods in the Core Data framework, an accessor creates an essential element of the Core Data stack if it does not yet exist. Having created it, the object can then return it quickly the next time it is asked for.
As you see throughout this book, Objective-C approaches subclassing and inheritance in more varied ways than many other object-oriented languages do. The basics of inheritance are simple. You declare a base object, which may have its own methods and instance variables. Following good Objective-C programming practice, the instance variables are accessed through accessors and are not visible to other classes.
GO TO Hour 9, “Declaring Properties in an Interface File,” p. 127, to see how to use declared properties to automatically generate accessors.
You can declare class methods in the class interface (a .h file) or in the implementation file (a .m file). Methods declared in the interface file are visible to other classes, but you can hide methods from the outside world by declaring them inside the implementation. Hiding methods in the implementation file helps to increase the encapsulation of the class.
GO TO “Using Class Extensions” in Hour 18, “Extending a Class with Categories and Extensions,” p. 245, to see how to hide your class’s methods.
You then proceed to override your base object. Each class that overrides the base object inherits the methods and variables of the base object. It can override them so that if your base object has a method called init, you can control how subclasses deal with that method. You have three basic choices:
Invoke—If your base class declares init, when you call init on a subclass, the base class’s init method is invoked.
Override—If you have declared an init method in a subclass, calling init on the subclass invokes the subclass’s init method.
Override and invoke—As part of a subclass’s override of a base class method, you can always call [super init] (or whatever the name of the base class method is).
This means that you can combine the functionality of the base class with additional or replacement functionality in its subclasses. If you subclass a subclass, you can use calls to super to invoke the various implementations of the method up through the base class. From the object-oriented standpoint, what you see is that the base object and its descendants all implement in one way or another this particular method—something that the abstract object should implement (such as reporting the balance in a bank account).
All of this is standard for object-oriented programming. Objective-C expands on the concept of inheritance in a variety of ways. Inheritance and subclassing provide an orderly way of integrating code from other objects into your own object. Primarily this is from superclasses of your own object.
However, Objective-C offers several ways to integrate code from other structures into your own object. In addition to inheritance, these are
Protocols—These are sets of methods that can be adopted by classes. The class that adopts the protocol then implements the methods of the protocol; if a class adopts a protocol, know that you can invoke any of the protocol’s methods. (A protocol’s methods can be marked as optional, and in that case you have to make certain that you are only invoking those methods that are required or are implemented.) Each class that adopts a protocol implements it in its own way, often with its own variables. The protocol structure in some ways implements some features of multiple inheritance. The methods of the protocol may appear in several classes that are otherwise unrelated. The idea that only the inheritance chain lets you share methods is augmented in this way.
Categories—Whereas protocols can be adopted by any class, categories consist of methods that are added to a specific class at runtime. You can use categories to add methods to an existing class for which you do not have the source code.
Extensions—Sometimes called anonymous categories, extensions are declared and implemented in the implementation of a class (typically the .m file). Extensions can add methods and properties to a class. They also can redefine properties (such as changing readonly to readwrite). They are now commonly used to declare methods and properties that are totally private to the class itself.
In this hour, you read about the design principle behind Objective-C as well as how it implements object-oriented programming objectives. In particular, you have seen how data abstraction and encapsulation are implemented in Objective-C so that you can write elegant, efficient, and maintainable object-oriented code. You have also seen how Objective-C implements a variety of architectures that allow for data reuse and sharing. With Objective-C, it is not just a matter of inheritance because you also have categories, extensions, and protocols to use.
Q. Why do categories, extensions, and protocols matter in Objective-C?
A. They allow you to reuse code in other ways than by creating subclasses. This can mean that your class hierarchies in Objective-C might be flatter than they are in languages in which the only way to share code is to subclass it.
Q. Why does encapsulation matter?
A. Along with data abstraction, it means that your objects are self-contained. The only attributes and functions that are exposed to the outside world are those that are common to all instances of the class. This can make ongoing maintenance easier.
1. If you override a method of a superclass, is it automatically invoked as well?
2. Why are accessors important?
1. No. If you want to invoke the method of a superclass, you must call it with code such as [super myMethod
]. Typically, that is either the first or last line of your override.
2. They allow you to set and get values for instance variables without directly touching the variables themselves. They also let you do some additional work such as adjusting or creating other variables and properties.
Explore the Cocoa frameworks on developer.apple.com or with Organizer in Xcode. If you read the overview of each one, you find a list of its classes, and you see which ones will be most important to you. Pay particular attention to UIKit in Cocoa Touch and AppKit in OS X.
18.118.137.67