您的位置:首页 > 编程语言 > Go语言

Customizing Existing Classes

2016-10-17 16:43 155 查看

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息