52个有效方法(26) - 勿在分类中声明属性
2018-09-05 本文已影响11人
SkyMing一C
-
分类机制,应将其理解为一种手段,目标在于扩展类的功能,而非封装数据。
-
分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。所以< 原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过objc/runtime添加属性 >
-
分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量(编译时会报警告)
#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
/**编译这段代码时,编译器会给出如下警告信息
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]
*/
- 方法调用的优先级:分类(最后参与编译的分类优先) --> 原来类 --> 父类。
使用Runtime给分类添加属性
@interface NSObject (EOC_CX)
/**
* 为每一个对象添加一个name属性
*/
@property (nonatomic,copy) NSString *name;
/**
* 为每个对象添加一个View属性
*/
@property (nonatomic,strong) UIView *booksView;
/**
* 为每个对象添加一个是否被选中属性
*/
@property(nonatomic, assign) BOOL isSelected;
@end
- 第一种写法
#import "NSObject + EOC_CX .h"
#import <objc/runtime.h>
// 使用对象关联需引入#import <objc/runtime.h>头文件
@implementation NSObject (EOC_CX)
// 用一个字节来存储key值,设置为静态私有变量,避免外界修改
static void *nameKey;
- (void)setName:(NSString *)name
{
// 将某个值与某个对象关联起来,将某个值存储到某个对象中
objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, &nameKey);
}
static void *booksViewKey;
- (void)setBooksView:(UIView *) booksView
{
objc_setAssociatedObject(self, &booksViewKey, booksView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *) booksView
{
return objc_getAssociatedObject(self, &booksViewKey);
}
//将bool类型转变成NSNumber类型来进行添加属性 这样储存策略为OBJC_ASSOCIATION_COPY_NONATOMIC
static void *isSelectedKey;
- (void)setIsSelected:(BOOL)isSelected {
objc_setAssociatedObject(self, &isSelectedKey, @(isSelected), OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (BOOL)isSelected {
return [((NSNumber *) objc_getAssociatedObject(self, &isSelectedKey)) boolValue];
}
@end
- 第二种写法(该用法取自于: forkingdog / UITableView-FDTemplateLayoutCell 中的用法)
#import "NSObject + EOC_CX .h"
#import <objc/runtime.h>
// 使用对象关联需引入#import <objc/runtime.h>头文件
@implementation NSObject (EOC_CX)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, _cmd);
//_cmd 代替了 &nameKey 或者 @selector(name).
}
- (void)setBooksView:(UIView *)booksView
{
objc_setAssociatedObject(self, @selector(booksView), booksView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *) booksView
{
return objc_getAssociatedObject(self, _cmd);
//_cmd 代替了 &booksKey 或者 @selector(booksView).
}
//将bool类型转变成NSNumber类型来进行添加属性 这样储存策略为OBJC_ASSOCIATION_COPY_NONATOMIC
- (void)setIsSelected:(BOOL)isSelected {
objc_setAssociatedObject(self, @selector(isSelected), @(isSelected), OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (BOOL)isSelected {
return [objc_getAssociatedObject(self, _cmd) boolValue];
//_cmd 代替了 &isSelectedKey 或者 @selector(isSelected).
}
@end
但是这样的形式,虽然可行,书中并不推荐,因为要把相似的代码写很多遍,而且在内存管理问题上容易出错,因为我们在为属性实现存取方法时,经常会忘记遵从其内存管理语义。比方说,你可能通过属性特质(attribute)修改了某个属性的内存管理语义。而此时还要记得,在设置方法中也得修改设置关联对象时所用的内存管理语义才行。<要保持属性声明中的内存管理的语义与runtime中传入的语义参数保持一致>。
要点
-
把封装数据所用的全部属性都定义在主接口中。
-
在“class-continuation分类”之外的其他分类中,可以定义存取方法,但尽量不要定义属性。