Customizing Existing Classes
每一个对象都应该有特定的用途,例如:为特定的信息建模,展示可视化内容,控制留信息等。有些时候我们会想得那些已经存在的类添加一些特定的方法,例如某个应用需要经常在界面上展示一些字符串信息,那么除了我们自己定义一个类来将string展示在屏幕上,那么我们试想一下能否直接调用NSString来展示这些信息?答案是肯定的至于如何实现那就看后文吧。
使用Categories来为存在的类添加方法
如果你要为一个已经存在的类添加方法,那么使用category是最方便的方法。
category的语法如下:
[code lang=”objc”]
/**声明category
*/
@interface ClassName (CategoryName)
@end
[/code]
一个category可以声明任何类,甚至是你连源码都没有的类,我们在category里声明的任何方法都可以跟着原始类一起被实例化。在运行的时候你在category里添加的方法与原始类中实现的方法是没有什么两样的,因此在category里添加的方法也可以被那些原始类的子类所继承。
我们继续使用XYZPerosn作为例子该类只有两个属性分别是firstname,lastname。如果我们要不断的打印这两个属性,如下:
Appleseed, John
Doe, Jane
Smith, Bob
Warwick, Kate
现在我要写一个可以将这两个属性拼接好的并返回结果的函数,但是有没有XYZPerson的源码怎么办呢?好我们可以用Category来解决。首先声明一个category,名字叫:XYZPersonNameDisplayAdditions
[code lang=”objc”]
#import "XYZPerson.h"
@interface XYZPerson (XYZPersonNameDisplayAdditions)
– (NSString *)lastNameFirstNameString;//返回拼接好的结果
@end
[/code]
接下来我们来实现这个category:
[code lang=”objc”]
#import "XYZPerson+XYZPersonNameDisplayAdditions.h"
@implementation XYZPerson (XYZPersonNameDisplayAdditions)
– (NSString *)lastNameFirstNameString {
return [NSString stringWithFormat:@"%@, %@", self.lastName, self.firstName];
}
@end
[/code]
一旦我们实现了category里地方法那么就可以在程序里使用这些方法,使用形式与原始类的方法的使用没有别的区别。下面的代码是在我们的程序里调用category中的方法:
[code lang=”objc”]
#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
[/code]
除了可以向现有的类添加方法,你还可以使用categories来将一个复杂的类分别在几个category中实现。例如:将用户自定义的界面元素单独放在一个category中,将几何图形的计算放在一个category中。另外你还可以对根据系统(IOS,MAC OS)来对category采用不同的实现方式。
category通常用来声明一些方法(包括成员方法,类方法),但是通常不用来声明附加属性。在category interface中声明一个属性时编译器不会报错,但是声明一个实体变量时编译器就会报错。这就意味着当声明了一个属性后,编译器不会为该属性生成实例变量以及其访问方法(getter,setter方法)。你可以自己定义该属性的访问方法,但是还是没有实例变量除非原始类中已有了该实例变量。
避免category中的方法名冲突
用category为已经存在的类添加方法时应该非常小心防止category中的方法名与原始类(包括其父类,或是该类的其他category)中的方法冲突。发生冲突后如果是出现在cocoa 或 cocoa touch中那么会有很严重的问题。
假如一个程序与一个webservice交互,在交互过程中需要对字符串以base64的编码方式进行编码。如果使用category来定义该编码方法,并将其添加到NSString中。但巧合的是在例外一个category中已经定义了该方法并也将其添加进了NSString,那么冲突发生了,在运行的时候只有一个实现方法会在本次的竞争中赢,另一个则成为未定义不起作用。
为了避免这种情况我们应该在定义category方法是为方法名添加前缀。如:
[code lang=”objc”]
@interface NSSortDescriptor (XYZAdditions)
+ (id)xyz_sortDescriptorWithKey:(NSString *)key ascending:(BOOL)ascending;
@end
[/code]
用extensions来实现类的扩展
extension与category多少有些相似,但是extension只能去扩展那些有源代码的类,因为编译器会在编译该类是去编译其extension。
extension的语法也与category比较相似:
[code lang=”objc”]
@interface XYZPerson ()
@property NSObject *extraProperty;
@end
[/code]
在extension中添加了属性后编译器会自动为该属性添加变量以及实现其getter和setter方法。如果声明方法的话那么该方法必须是在原始类中已经实现了的。
使用extension来隐藏私有信息
interface(在.h文件中的)通常是定义了与外界交互的方式,换句话说interface(在.h文件中的)是类的公共部分。当我们把interface写在该类的实现文件里(.m文件)里时他是class的私有部分,不能够被外界的其他对象访问。语法如下:
[code lang=”objc”]
@interface XYZPerson ()
@property (readwrite) NSString *uniqueIdentifier;
@end
@implementation XYZPerson
…
@end
[/code]