Customizing Existing Classes

Customizing Existing Classes

对象定义了明确的任务,比如model化指定信息、展示可视化内容、控制流程。一个类的interface定义了和其他类的交互方式以便以完成任务。
有时候你会发现,你希望拓展现有类。Objective-C提供两种途径来拓展现有类:Categories(类别)和Class Extensions(类拓展)。

Category

如果需要给已有类添加Method,最简单的方法就是使用Category。

声明

声明类别的语法是通过@interface关键字,类似声明Class,但是没有继承关系,而是在圆括号中声明类别的名字。例如

@interface ClassName (CategoryName)

@end

1、可以给任何类声明类别,即使你没有源码,比如Cocoa Touch的类。
2、可以像子类一样访问所有的实例变量
3、在runtime,category和原类实现的方法没有区别
4、category通常在独立的header file 和 implemented file中,所以使用时需要导入头文件,否则编译器报错
比如所XYZPerson有很多属性,其中包括lastName和firstName,现在需要直接返回完整的姓名,那么可以通过category添加方法

#import "XYZPerson.h"

@interface XYZPerson (XYZPersonNameDisplayAdditions)
- (NSString *)lastNameFirstNameString;
@end


#import "XYZPerson+XYZPersonNameDisplayAdditions.h"

@implementation XYZPerson (XYZPersonNameDisplayAdditions)
- (NSString *)lastNameFirstNameString {
    return [NSString stringWithFormat:@"%@, %@", self.lastName, self.firstName];
}
@end

然后在导入XYZPerson+XYZPersonNameDisplayAdditions.h的任何类中使用

#import "XYZPerson+XYZPersonNameDisplayAdditions.h"
@implementation SomeObject
- (void)someMethod {
    XYZPerson *person = [[XYZPerson alloc] initWithFirstName:@"John"
                                                    lastName:@"Doe"];
    XYZShoutingPerson *shoutingPerson =
                        [[XYZShoutingPerson alloc] initWithFirstName:@"Monica"
                                                            lastName:@"Robinson"];

    NSLog(@"The two people are %@ and %@",
         [person lastNameFirstNameString], [shoutingPerson lastNameFirstNameString]);
}
@end

声明Method

Category可以声明实例方法和类方法;可以使用@property语法声明property,但是不能声明实例变量,这就意味着编译器不能自动生成实例变量,不能为property自动生成setter和getter方法。

//例如在类别中@property一个name,收到如下警告
Property 'name' requires method 'name' to be defined - use @dynamic or provide a method implementation in this category

Property 'name' requires method 'setName:' to be defined - use @dynamic or provide a method implementation in this category

尽管可以自己实现set和get方法,但是不能自己声明实例变量来保持数据(除非使用原类的实例变量)。
既然category添加的方法在runtime无差别,可以使用runtime接口来验证一下
给Data类声明一个类别,类别中添加property和Method

#import "Data.h"

@interface Data (DataCategory)

@property (nonatomic,copy)NSString *name;

+(void)classMethod;
-(void)ivarMethod;
@end


#import "Data+DataCategory.h"

@implementation Data (DataCategory)

+(void)classMethod
{
    NSLog(@"调用classMethod");

}

-(void)ivarMethod
{
    NSLog(@"调用ivarMethod");
}
@end

然后运行一下代码查看结果

/* 获取实例变量列表 */
unsigned int count = 0;
Ivar *list =  class_copyIvarList([Data class], &count);
for (int i = 0; i < count ; i ++)
{
   Ivar var = list[i];
   const char *name_var = ivar_getName(var);
   const char *type_var = ivar_getTypeEncoding(var);//有对照表
   ptrdiff_t offset_var = ivar_getOffset(var);//获取变量内存偏移量

   NSLog(@"Ivar Name:%@ TypeEncoding:%@ Offset:%td",[NSString stringWithUTF8String:name_var],[NSString stringWithUTF8String:type_var],offset_var);
}
free(list);


/* property列表 */
unsigned int property_count = 0;
objc_property_t *property_list =  class_copyPropertyList([Data class], &property_count);

for (int i = 0; i < property_count ; i ++)
{
   objc_property_t property = property_list[i];
   const char *name_property = property_getName(property);//名称
   const char *name_attributes = property_getAttributes(property);//属性字符串
   NSLog(@"Property:%@  attributes:%@",[NSString stringWithUTF8String:name_property],[NSString stringWithUTF8String:name_attributes]);

}
free(property_list);//必须free

/* 获取实例方法列表 */
unsigned int count_method = 0;
Method *method_list = class_copyMethodList([Data class], &count_method);
for (int i = 0; i < count_method; i ++)
{
   Method method = method_list[i];
   SEL sel_Method = method_getName(method);
   NSLog(@"实例方法列表:%@",NSStringFromSelector(sel_Method));

}

/* 获取类方法列表 */
unsigned int count_method2 = 0;
Method *method_list2 = class_copyMethodList(object_getClass([Data class]), &count_method2);
for (int i = 0; i < count_method2; i ++)
{
   Method method = method_list2[i];
   SEL sel_Method = method_getName(method);
   NSLog(@"类方法列表:%@",NSStringFromSelector(sel_Method));
}
free(method_list2);

//输出结果
Property:name  attributes:T@"NSString",C,N
实例方法列表:ivarMethod
类方法列表:classMethod

从输出结果可以验证:
1、@property语法不能生成实例变量和访问方法
2、可以正常声明实例和类方法

注意方法名称冲突

由于category中声明的方法被增加到原类的方法列表中,名称一定不能冲突。比如说,你的Method名称和原类同名,或者Category A 和Category B有相同的Method,这将导致其中一个不能正常使用,编译器同时发出警告。解决方案是像new class一样添加前缀:小写前缀+下划线_+方法名称。例如

@interface NSSortDescriptor (XYZAdditions)
+ (id)xyz_sortDescriptorWithKey:(NSString *)key ascending:(BOOL)ascending;
@end

功能

1、拓展现有类Method
2、可以把复杂类分开实现。例如NSString的UIStringDrawing类别

Class Extensions

声明

类扩展扩展类的内部实现。类扩展和Category类似,但是只能添加到开源类。类扩展中声明的方法,都在原类的@implementation代码块中实现。所以不能给SDK中的不开源类添加类拓展,即时Xcode中可以添加但是没地方实现这些方法。
声明类扩展的语法和Category类似

@interface ClassName ()

@end

由于圆括号中没有名字,所以被称为匿名Category。和类别不同的是,类扩展可以添加实例变量和Property

@interface XYZPerson ()
{
    id _someCustomInstanceVariable;
}
@property NSObject *extraProperty;
@end

1、编译器自动生成实例变量和访问器方法
2、类扩展方法必须在原类的implementation部分实现

隐藏私有信息

类的interface用来定义公共接口。类扩展一般用来定义私有接口和property。比如说XYZPerson有一个公共的property,但是不希望被其他对象之间修改值,所以声明为只读readonly,然后提供一个方法来进行修改值。在类内部希望能够之间修改值,所以使用类扩展然后把该property声明为可读写readwrite。例如

@interface XYZPerson : NSObject
...
@property (readonly) NSString *uniqueIdentifier;
- (void)assignUniqueIdentifier;
@end


@interface XYZPerson ()
@property (readwrite) NSString *uniqueIdentifier;
@end

@implementation XYZPerson
...
@end

提示:
1、property默认readwrite,可以不写,但是可以起到对比作用,增加可读性
2、readonly的界限可以通过dynamic runtime features来打破。比如NSObject的performSelector:系列方法,或者runtime的method_invoke()函数
解读:@property本质通过set和get方法实现。readonly即在header文件中只声明getter方法,类扩展重新声明为可读性,即在.m文件中补上了setter方法。

抽象类 和 Delegate

尽管类别和类扩展可以很方便的扩展现有类,但是有时候不是最好的选择。面向对象编程的目标之一就是写出可以重复利用的代码。比如说写一个view来展示可视化信息,
1、与其努力的关注如何布局和展示内容,不如利用继承把需要做决定的部分留给子类进行重写。此类情况尽管父类可以服用,但是每次都要创建子类才可以使用–抽象类,例如CAAnimation。
2、把需要做决定的部分交给delegate对象,其它部分代理可以服用。经典例子如UITableView。

Runtime扩展类

Objective-C通过Runtime系统展现动态性。方法调用的决定时间不是在编译器决定,实在运行时决定。可以通过Associative References和runtime直接交互达到扩展现有类的目的,与类扩展不同,他不会影响原类的声明和实现就可以给对象链接另一个对象,所以可以扩展任何类。

/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object 目标对象
 * @param key The key for the association.
 * @param value 添加对象。 Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 */
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);

/** 
 * 返回关联对象
 * 
 * @param object 目标对象
 * @param key The key for the association.
 * 
 * @return 返回对象
 */   
id objc_getAssociatedObject(id object, const void *key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);

/** 
 * 移除所有关联对象
 * 
 * @param object 目标对象
 * 
 * @note 此函数作用用来还原到初始状态。若果要要移除某个关联请使用:objc_setAssociatedObject 传nil值
 * 
 */
void objc_removeAssociatedObjects(id object)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

给Viewcontroller类关联一个Data的data对象,代码如下

//随意一个类
Data *data = [[Data alloc] init];

//关联
objc_setAssociatedObject(self, "data", data, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
id objc_data1 = objc_getAssociatedObject(self, "data");
NSLog(@"objc_data1:%@",objc_data1);

//移除单个
objc_setAssociatedObject(self, "data", nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
id objc_data2 = objc_getAssociatedObject(self, "data");
NSLog(@"objc_data2:%@",objc_data2);

//移除所有
objc_setAssociatedObject(self, "data", data, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_removeAssociatedObjects(self);
id objc_data3 = objc_getAssociatedObject(self, "data");
NSLog(@"objc_data3:%@",objc_data3);

//输出结果
objc_data1:<Data: 0x6080000047b0>
objc_data2:(null)
objc_data3:(null)

参考文献:Customizing Existing Classes

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值