Effective Objective-C 2.0:Item 26: Avoid Properties in Categories

本文探讨了在Objective-C中如何正确地在类别里使用属性,并解释了为什么不应在类别中声明属性来封装数据。文章提供了实例说明如何解决因类别无法合成实例变量导致的问题,并推荐了使用关联对象的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Item 26: Avoid Properties in Categories

A property is a way of encapsulating data (see Item 6). Although it is technically possible to declare a property in a category, you should avoid doing so if possible. The reason is that it is impossible for a category, except the class-continuation category (see Item 27), to add instance variables to a class.Therefore, it is also impossible for the category to synthesize an instance variable to back the property.

Suppose that after reading Item 24, you have decided to use categories to split up into distinct fragments your implementation of a class representing a person. You might decide to have a friendship category for all the methods relating to manipulating the list of friends associated with a person. Without knowing about the problem described earlier, you may also put the property for the list of friends within the friendship category, like so:

#import <Foundation/Foundation.h>

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

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

@implementation EOCPerson
// Methods
@end

@interface EOCPerson (Friendship)
@property (nonatomic, strong) NSArray *friends;
- (BOOL)isFriendsWith:(EOCPerson*)person;
@end

@implementation EOCPerson (Friendship)
// Methods
@end

If you compile this, however, you would end up with compiler warnings:

warning: property 'friends' requires method 'friends' to be
defined - use @dynamic or provide a method implementation in
this category [-Wobjc-property-implementation]
warning: property 'friends' requires method 'setFriends:' to be
defined - use @dynamic or provide a method implementation in
this category [-Wobjc-property-implementation]

This slightly cryptic warning means that instance variables cannot be synthesized by a category and, therefore, that the property needs to have the accessor methods implemented in the category.Alternatively, the accessor methods can be declared @dynamic, meaning that you are declaring that they will be available at runtime but cannot be seen by the compiler. This might be the case if you are using the message-forwarding mechanism (see Item 12) to intercept the methods and provide the implementation at runtime.

To get around the problem of categories not being able to synthesize instance variables, you can useassociated objects (see Item 10). For the example, you would need to implement the accessors within the category as follows:

#import <objc/runtime.h>

static const char *kFriendsPropertyKey = "kFriendsPropertyKey";

@implementation EOCPerson (Friendship)

- (NSArray*)friends {
    return objc_getAssociatedObject(self, kFriendsPropertyKey);
}

- (void)setFriends:(NSArray*)friends {
    objc_setAssociatedObject(self,
                             kFriendsPropertyKey,
                             friends,
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

This works, but it’s less than ideal. It’s a fair amount of boilerplate and is prone to errors with memory management because it’s easy to forget that the property is implemented like this. For example, you may change the memory-management semantics by changing the property attributes. However, you would also need to remember to change the memory-management semantics of the associated object in the setter. So although this is not a bad solution, it is not one that I recommend.

Also, you may want the instance variable backing the friends array to be a mutable array. You could take a mutable copy, but it’s yet another vector for confusion to enter your code base. It is therefore much cleaner to define properties in the main interface definition rather than to define them in categories.

In this example, the correct solution is to keep all the property definitions in the main interface declaration. All the data encapsulated by a class should be defined in the main interface, the only place where instance variables (the data) can be defined. Since they are just syntactic sugar for defining an instance variable and associated accessor methods, properties fall under the same rule. Categories should be thought of as a way to extend functionality of the class, not the data that it encapsulates.

That being said, sometimes read-only properties can be successfully used within categories. For example, you might want to create a category on NSCalendar to return an array containing the months as strings. Since the method is not accessing any data and the property is not backed by an instance variable, you could implement the category like this:

@interface NSCalendar (EOC_Additions)
@property (nonatomic, strong, readonly) NSArray *eoc_allMonths;
@end

@implementation NSCalendar (EOC_Additions)
- (NSArray*)eoc_allMonths {
    if ([self.calendarIdentifier
            isEqualToString:NSGregorianCalendar])
    {
        return @[@"January", @"February",
                 @"March", @"April",
                 @"May", @"June",
                 @"July", @"August",
                 @"September", @"October",
                 @"November", @"December"];
    } else if ( /* other calendar identifiers */ ) {
        /* return months for other calendars */
    }
}
@end

Autosynthesis of an instance variable to back the property will not kick in, because all the required methods (only one in this read-only case) have been implemented. Therefore, no warnings will be emitted by the compiler. Even in this situation, however, it is generally better to avoid using a property.A property is meant to be backed by data held by the class. A property is for encapsulating data. In this example, you would instead declare the method to retrieve the list of months within the category:

@interface NSCalendar (EOC_Additions)
- (NSArray*)eoc_allMonths;
@end

Things to Remember

Image Keep all property declarations for encapsulated data in the main interface definition.

Image Prefer accessor methods to property declarations in categories, unless it is a class-continuation category.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值