Effective Objective-CItem 24: Use Categories to Break Class Implementations into Manageable Segments

Item 24: Use Categories to Break Class Implementations into Manageable Segments

A class can easily become bloated with many methods all interspersed throughout a huge implementation file. Sometimes, that’s just the way it is, and no amount ofrefactoring to split up the class can make the situation better. In that case, Objective-C categories can be used to great effect to split up a class into logical subsections that aid not only development but also debugging.

Consider a class that models a person. The person might have a few methods available on it:

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName:(NSString*)firstName
            andLastName:(NSString*)lastName;

/* Friendship methods */
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;

/* Work methods */
- (void)performDaysWork;
- (void)takeVacationFromWork;

/* Play methods */
- (void)goToTheCinema;
- (void)goToSportsGame;

@end

The implementation for such a class would contain all methods in a long list in one big file. As more methods are added to the class, the file will only get longer and more unmanageable. So it is often useful to split up such a class into separate, distinct portions. For example, the preceding could be rewritten to make use of categories:

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName:(NSString*)firstName
            andLastName:(NSString*)lastName;
@end

@interface EOCPerson (Friendship)
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;
@end

@interface EOCPerson (Work)
- (void)performDaysWork;
- (void)takeVacationFromWork;
@end

@interface EOCPerson (Play)
- (void)goToTheCinema;
- (void)goToSportsGame;
@end

Now, each distinct part of the class is split into separate categories of methods. Not surprisingly, this language feature is called categoriesIn the example, the bases of the class, including the properties and initializer, are declared within the main implementation. Additional sets of methods, relating to different types of actions, are split into categories.

The entire class could still be defined within one interface file and one implementation file, but as the categories grow, the single implementation file could easily become unmanageable. In that case, categories could be extracted into their own files. For example, the categories in EOCPerson could be extracted into separate files:

Image EOCPerson+Friendship(.h/.m)

Image EOCPerson+Work(.h/.m)

Image EOCPerson+Play(.h/.m)

For example, the friendship category would look like this:

// EOCPerson+Friendship.h
#import "EOCPerson.h"

@interface EOCPerson (Friendship)
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;
@end

// EOCPerson+Friendship.m
#import "EOCPerson+Friendship.h"

@implementation EOCPerson (Friendship)
- (void)addFriend:(EOCPerson*)person {
    /* ... */
}
- (void)removeFriend:(EOCPerson*)person {
    /* ... */
}
- (BOOL)isFriendsWith:(EOCPerson*)person {
    /* ... */
}
@end

The class has been split up into much more manageable chunks of code that can be inspected individually. You must then remember to import the EOCPerson.hheader along with any category headers anywhere that the category methods are required. But this is a good way of making your code more manageable.

Even if a class is not too large, using categories to split it into subsections can be a useful way to segment code into functional areas. An example of such a class that does this within Cocoa is NSURLRequest and its mutable counterpart,NSMutableURLRequest. This class performs requests to obtain data from a URL and is used mostly with HTTP to obtain data from a server on the Internet somewhere, but the class is generic and can be used with other protocols as well. However, HTTP requests require additional information to be set on them beyond what standard URL requests require, such as the HTTP methods (GET, POST, etc.) or HTTP headers.

But NSURLRequest is not easily subclassed, since it wraps a set of C functions that act on a CFURLRequest data structure, including all the HTTP methods. So the HTTP-specific methods are simply added to NSURLRequest as a category calledNSHTTPURLRequest, and the mutation methods are added toNSMutableURLRequest as a category called NSMutableHTTPURLRequest. This way, all the underlying CFURLRequest functions are wrapped within the same Objective-C class, but the HTTP-specific methods are split into a separate area to avoid confusion such that users might wonder why they are able to set the HTTP method on a request object that uses the protocol FTP.

Another useful reason to split up a class into categories is for debugging purposes; the category name is added to the symbol for all methods within that category. For example, the addFriend: method has the following symbol name:

-[EOCPerson(Friendship) addFriend:]

When it appears in backtraces in a debugger, it will look something like this:

frame #2: 0x00001c50 Test'-[EOCPerson(Friendship) addFriend:]
+ 32 at main.m:46

The category name within the backtrace can be extremely useful for pinpointing exactly which functional area of the class the method relates to, which is particularly useful when certain methods should be regarded as private. In that case, it might be useful to create a category called Private that contains all these methods. Such categories’ methods are generally used only internally to a class or framework and not exposed. If a user finds such a method—perhaps by reading a backtrace—the private in the name should help to indicate that the method should not be used directly. In a way, it is a method of self-documenting code.

The idea of a Private category is useful when creating a library that will be shared by other developers. Often, some methods should not form part of the public API but are quite nice to use within the library itself. In this scenario, creating aPrivate category is a good option, since its header can be imported wherever the methods need to be used within the library. If that category header is not released as part of the library’s release, consumers of the library would have no idea that those private methods exist.

Things to Remember

Image Use categories to split a class implementation into more manageable fragments.

Image Create a category called Private to hide implementation detail of methods that should be considered as private.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值