Ry’s Objective-C Tutorial---Categories
2015-11-22 00:03
337 查看
Tutorials
Purchases
About
You’re reading Ry’s Objective-C Tutorial |
Categories
Categories are a way to split a single class definition into multiple files. Their goal is to ease the burden of maintaining large code bases by modularizing a class. This prevents your source code from becoming monolithic 10000+ line files that are impossibleto navigate and makes it easy to assign specific, well-defined portions of a class to individual developers.
Using multiple files to implement a class
In this module, we’ll use a category to extend an existing class without touching its original source file. Then, we’ll learn how this functionality can be used to emulate protected methods. Extensions are a close relative to categories, so we’ll be taking
a brief look at those, too.
Setting Up
Before we can start experimenting with categories, we need a base class to work off of. Create or change your existing Carinterface to the following:
// Car.h
#import
<Foundation/Foundation.h>
@interface
Car:
NSObject
@property
(
copy
)
NSString
*
model
;
@property
(
readonly
)
double
odometer
;
-
(
void
)
startEngine
;
-
(
void
)
drive
;
-
(
void
)
turnLeft
;
-
(
void
)
turnRight
;
@end
[/code]
The corresponding implementation just outputs some descriptive messages so we can see when different methods get called:
// Car.m
#import
"Car.h"
@implementation
Car
@synthesize
model
=
_model
;
-
(
void
)
startEngine
{
NSLog
(
@"Starting the %@'s engine"
,
_model
);
}
-
(
void
)
drive
{
NSLog
(
@"The %@ is now driving"
,
_model
);
}
-
(
void
)
turnLeft
{
NSLog
(
@"The %@ is turning left"
,
_model
);
}
-
(
void
)
turnRight
{
NSLog
(
@"The %@ is turning right"
,
_model
);
}
@end
[/code]
Now, let’s say you want to add another set of methods related to car maintenance. Instead of cluttering up these
Car.hand
Car.mfiles,
you can place the new methods in a dedicated category.
Creating Categories
Categories work just like normal class definitions in that they are composed of an interface and an implementation. To add a new category to your Xcode project, create a new file and choose the Objective-Ccategory template under iOS > Cocoa Touch. Use
Maintenancefor the Category field
and
Carfor Category on.
Creating the
Maintenancecategory
The only restriction on category names is that they don’t conflict with other categories on the same class. The canonical file-naming convention is to use the class name and the category name separated by a plus sign, so you should find a
Car+Maintenance.hand
a
Car+Maintenance.min Xcode’s Project Navigator after saving the above category.
As you can see in
Car+Maintenance.h, a category interface looks exactly like a normal interface, except the class name is followed by the category name in parentheses. Let’s
go ahead and add a few methods to the category:
// Car+Maintenance.h
#import
"Car.h"
@interface
Car
(Maintenance)
-
(
BOOL
)
needsOilChange
;
-
(
void
)
changeOil
;
-
(
void
)
rotateTires
;
-
(
void
)
jumpBatteryUsingCar:
(
Car
*
)
anotherCar
;
@end
[/code]
At runtime, these methods become part of the
Carclass. Even though they’re declared in a different file, you will be able to access them as if they were defined in the original
Car.h.
Of course, you have to implement the category interface for the above methods to actually do anything. Again, a category implementation looks almost exactly like a standard
implementation, except the category name appears in parentheses after the class name:
// Car+Maintenance.m
#import
"Car+Maintenance.h"
@implementation
Car
(Maintenance)
-
(
BOOL
)
needsOilChange
{
return
YES
;
}
-
(
void
)
changeOil
{
NSLog
(
@"Changing oil for the %@"
,
[
self
model
]);
}
-
(
void
)
rotateTires
{
NSLog
(
@"Rotating tires for the %@"
,
[
self
model
]);
}
-
(
void
)
jumpBatteryUsingCar:
(
Car
*
)
anotherCar
{
NSLog
(
@"Jumped the %@ with a %@"
,
[
self
model
],
[
anotherCar
model
]);
}
@end
[/code]
It’s important to note that a category can also be used to override existing methods in the base class (e.g., the
Carclass’s
drivemethod),
but you should never do this. The problem is that categories are a flat organizational structure. If you override an existing method in
Car+Maintenance.m,
and then decide you want to change its behavior again with another category, there is no way for Objective-C to know which implementation to use. Subclassing is almost always a better option in such a situation.
Using Categories
Any files that use an API defined in a category need to import that category header just like a normal class interface. After importingCar+Maintenance.h, all of its methods will
be available directly through the
Carclass:
// main.m
#import
<Foundation/Foundation.h>
#import
"Car.h"
#import
"Car+Maintenance.h"
int
main
(
int
argc
,
const
char
*
argv
[])
{
@autoreleasepool
{
Car
*
porsche
=
[[
Car
alloc
]
init
];
porsche
.
model
=
@"Porsche 911 Turbo"
;
Car
*
ford
=
[[
Car
alloc
]
init
];
ford
.
model
=
@"Ford F-150"
;
// "Standard" functionality from Car.h
[
porsche
startEngine
];
[
porsche
drive
];
[
porsche
turnLeft
];
[
porsche
turnRight
];
// Additional methods from Car+Maintenance.h
if
([
porsche
needsOilChange
])
{
[
porsche
changeOil
];
}
[
porsche
rotateTires
];
[
porsche
jumpBatteryUsingCar:
ford
];
}
return
0
;
}
[/code]
If you remove the import statement for
Car+Maintenance.h, the
Carclass will revert to its original
state, and the compiler will complain that
needsOilChange,
changeOil, and the rest of the
methods from the
Maintenancecategory don’t exist.
“Protected” Methods
But, categories aren’t just for spreading a class definition over several files. They are a powerful organizational tool that allow arbitrary files to “opt-in” to a portion of an API by simply importing the category. To everybody else, that API remains hidden.Recall from the Methods module that protected methods don’t actually
exist in Objective-C; however, the opt-in behavior of categories can be used to emulate protected access modifiers. The idea is to define a “protected” API in a dedicated
category, and only import it into subclass implementations. This makes the protected methods available to subclasses, but keeps them hidden from other aspects of the application. For example:
// Car+Protected.h
#import
"Car.h"
@interface
Car
(Protected)
-
(
void
)
prepareToDrive
;
@end
[/code]
// Car+Protected.m
#import
"Car+Protected.h"
@implementation
Car
(Protected)
-
(
void
)
prepareToDrive
{
NSLog
(
@"Doing some internal work to get the %@ ready to drive"
,
[
self
model
]);
}
@end
[/code]
The
Protectedcategory shown above defines a single method for internal use by
Carand its
subclasses. To see this in action, let’s modify
Car.m’s
drivemethod to use the protected
prepareToDrivemethod:
// Car.m
#import
"Car.h"
#import
"Car+Protected.h"
@implementation
Car
...
-
(
void
)
drive
{
[
self
prepareToDrive
];
NSLog
(
@"The %@ is now driving"
,
_model
)
;
}
...
[/code]
Next, let’s take a look at how this protected method works by creating a subclass called
Coupe. There’s nothing special about the interface, but notice how the implementation
opts-in to the protected API by importing
Car+Protected.h. This makes it possible to use the protected
prepareToDrivemethod
in the subclass. If desired, you can also override the protected method by simply re-defining it in
Coupe.m.
// Coupe.h
#import
"Car.h"
@interface
Coupe:
Car
// Extra methods defined by the Coupe subclass
@end
[/code]
// Coupe.m
#import
"Coupe.h"
#import
"Car+Protected.h"
@implementation
Coupe
-
(
void
)
startEngine
{
[
super
startEngine
];
// Call the protected method here instead of in `drive`
[
self
prepareToDrive
];
}
-
(
void
)
drive
{
NSLog
(
@"VROOOOOOM!"
);
}
@end
[/code]
To enforce the protected status of the methods in
Car+Protected.h, it should only be made available to subclass implementations—do notimport
it into other files. In the following
main.m, you can see the protected
prepareToDrivemethod
called by
[ford drive]and
[porsche startEngine], but the compiler will complain if you try
to call it directly.
// main.m
#import
<Foundation/Foundation.h>
#import
"Car.h"
#import
"Coupe.h"
int
main
(
int
argc
,
const
char
*
argv
[])
{
@autoreleasepool
{
Car
*
ford
=
[[
Car
alloc
]
init
];
ford
.
model
=
@"Ford F-150"
;
[
ford
startEngine
];
[
ford
drive
];
// Calls the protected method
Car
*
porsche
=
[[
Coupe
alloc
]
init
];
porsche
.
model
=
@"Porsche 911 Turbo"
;
[
porsche
startEngine
];
// Calls the protected method
[
porsche
drive
];
// "Protected" methods have not been imported,
// so this will *not* work
// [porsche prepareToDrive];
SEL
protectedMethod
=
@selector
(
prepareToDrive
);
if
([
porsche
respondsToSelector:
protectedMethod
])
{
// This *will* work
[
porsche
performSelector:
protectedMethod
];
}
}
return
0
;
}
[/code]
Notice that you can access
prepareToDrivedynamically through
performSelector:.
Once again, all methods in Objective-C are public, and there is no way to truly hide them from client code. Categories are merely a convention-based way to control which
parts of an API are visible to which files.
Extensions
Extensions are similar to categories in that they let you add methods to a class outside of the main interface file. But, in contrast to categories, an extension’s API must be implemented in the main implementationfile—it cannot be implemented in a category.
Remember that private methods can be emulated by adding them to the implementation but not the interface. This works when you have only a few private methods, but can become unwieldy for larger classes. Extensions solve this problem by letting you declare a formalprivate
API.
For example, if you wanted to formally add a private
engineIsWorkingmethod to the
Carclass
defined above, you could include an extension in
Car.m. The compiler complains if the method isn’t defined in the main
@implementationblock,
but since it’s declared in
Car.minstead of
Car.h, it remains a private method.
The extension syntax looks like an empty category:
// Car.m
#import
"Car.h"
// The class extension
@interface
Car
()
-
(
BOOL
)
engineIsWorking
;
@end
// The main implementation
@implementation
Car
@synthesize
model
=
_model
;
-
(
BOOL
)
engineIsWorking
{
// In the real world, this would probably return a useful value
return
YES
;
}
-
(
void
)
startEngine
{
if
([
self
engineIsWorking
])
{
NSLog
(
@"Starting the %@'s engine"
,
_model
);
}
}
...
@end
[/code]
In addition to declaring formal private API’s, extensions can be used to re-declare properties from the public interface. This is often used to make properties internally behave as read-write properties while remaining read-only to other objects. For instance,
if we change the above class extension to:
// Car.m
#import
"Car.h"
@interface
Car
()
@property
(
readwrite
)
double
odometer
;
-
(
BOOL
)
engineIsWorking
;
@end
...
[/code]
We can then assign values to
self.odometerinside of the implementation, but trying to do so outside of
Car.mwill
result in a compiler error.
Again, re-declaring properties as read-write and creating formal private API’s isn’t all that useful for small classes. Their real utility comes into play when you need to organize larger frameworks.
Extensions used to see much more action before Xcode 4.3, back when private methods had to be declared before they were used. This was inconvenient for many developers,
and extensions provided a workaround by acting as forward-declarations of private methods. So, even if you don’t use the above pattern in your own projects, you’re likely to encounter it at some point in your Objective-C career.
Summary
This module covered Objective-C categories and extensions. Categories are a way to modularize a class by spreading its implementation over many files. Extensions provide similar functionality, except its API must be declared in the mainimplementationfile.
Outside of organizing large code libraries, one of the most common uses of categories is to add methods to built-in data types like
NSStringor
NSArray.
The advantage of this is that you don’t have to update existing code to use a new subclass, but you need to be very careful not to override existing functionality. For small personal projects, categories really aren’t worth the trouble, and sticking with standard
tools like subclassing and protocols will save you some debugging headaches down the road.
In the next module, we’ll explore another organizational tool called blocks. Blocks are a way to represent and pass around arbitrary statements. This opens the door to a whole new world of programming paradigms.
Mailing List
Sign up for my low-volume mailing list to find out when new content is released. Next up is a comprehensive Swift tutorialplanned for late January.
Email Address:
You’ll only receive emails when new tutorials are released, and your contact information will never be shared with third parties. Click
here to unsubscribe.
© 2012-2014
RyPress.com
All Rights Reserved
Terms of Service
Privacy Policy
相关文章推荐
- Ry’s Objective-C Tutorial---Protocols
- Ry’s Objective-C Tutorial---Methods
- Ry’s Objective-C Tutorial---Properties
- Objective-c(3)
- Ry’s Objective-C Tutorial---Classes
- Ry’s Objective-C Tutorial---Founctions
- Ry’s Objective-C Tutorial---C Basics
- 【iOS】如何在Objective-C中声明Block?
- lua 垃圾回收标记函数 reallymarkobject
- Object-C 学习第一天
- Objective-C编码规范:26个方面解决iOS开发问题
- jsonObject获取json串的值
- Objective-C中NSString对象的retainCount
- java中Object类与string类及其字符串处理方法
- 针对【ObjectStateManager 中已存在具有同一键的对象。ObjectStateManager 无法跟踪具有相同键的多个对象。】的解决方案
- objective c实现配置文件+反射
- Can not deserialize instance of java.lang.String out of START_OBJECT token
- Objective-C 的属性与合成方法使用详解
- object-UI之基本控件
- object-UI之基本控件