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、拓展现有类Method2、可以把复杂类分开实现。例如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
相关文章推荐
- 自定义现有类(Customizing Existing Classes)
- 《Programming with Objective-C》第五章 Customizing Existing Classes
- Objective-C 苹果开发文档 05 Customizing Existing Classes
- Categories Add Methods to Existing Classes
- 第七章 Customizing Concurrency Classes(自定义并发类)【下】
- Customizing the Appearance of an Existing Control by Creating a ControlTemplate
- Asset Catalog Help (七)---Customizing Image Sets for Size Classes
- 第七章 Customizing Concurrency Classes(自定义并发类)【上】
- Adding Functionality to Existing Thread safe Classes
- 第十一节--重载 -- Classes and Objects in PHP5 [11]
- BDNtv: Deriving a model from an existing database with ECO II in Delphi 2005
- Unloading and Reloading classes
- A Quick Guide to the Window Classes for wxWidgets
- WTL for MFC Programmers, Part I - ATL GUI Classes
- Debug with anonymous inner classes
- netTiers New Query Builder Classes
- Classes and Objects in PHP5(5)
- Dottext.Web.UI.Handlers.BlogExistingPageHandler
- 第四节--构造函数和析构函数 -- Classes and Objects in PHP5 [4](转)
- WTL06:GDI Classes, Common Dialogs, and Utility Classes