三 category和enumeration的用法
继承是面向对象程序设计的一个重要特性,但是继承的一些缺点也越来越多被人们意识到。因为继承有时候会破坏类的封装性,使子类可以使用父类的一些非pubic的方法。另外当继承树大到一定程度的时候相信许多程序员都不愿意看到,因为毕竟程序不仅仅是要让计算机运行的,更重要的一点就是要人能够看懂,否则这样的程序也只能束之高阁,供人膜拜了。根据研究表明继承的层次维持3层以下是最容易让人理解。
所以继承有时候并不表现的那么有用,其实在设计模式中,适配器模式就可以解释用继承是多么的糟糕。那么不用继承objective c如何扩展一个类那,那么Apple的工程师就设计了category这个新语法特性。它的功能就是实现类的扩展而完全不用继承。例如我们要给Nsstring类增加一个新的功能计算Nsstring的长度,并且返回值为NSNumber。假如你要用继承实现
@interface NSNewString:Nsstring
- (NSNumber)lengthAsNumber;
@end
@implement
- (NSNumber *) lengthAsNumber
{
unsigned int length = [self length];
return ([NSNumber numberWithUnsignedInt: length]);
}
@end
NSNewString *str = [Nsstring stringWithFormat:"test"]然后你就会得到编译器的警告,stringWithFormat返回的是Nsstring而你要它成为NSNewString,这种向下转化是极不安全的。所以你写的子类当调用系统API的时候,无法获得你子类的对象结果你的方法就很难用到此处了。
而下面用category实现就大不一样了。实现如下:首先要给这个category定义一个名字
@interface Nsstring (NumberConvenience)
- (NSNumber *) lengthAsNumber;
@end
@implementation Nsstring (NumberConvenience)
- (NSNumber *) lengthAsNumber
{
unsigned int length = [self length];
return ([NSNumber numberWithUnsignedInt: length]);
} // lengthAsNumber
@end
这样我就是用系统的API获得的Nsstring也可以用我这个方法了。下面就说下category使用的时候应该注意的地方。
category有2点限制首先你不能在里面定义任何实例变量,它只是方法的扩展,否则你就真的要用继承了。其次category里面的方法不能和原来类中的方法冲突,否则原来类中的方法就无效了。其实cocoa程序设计中同样有方法定义一个类中的方法为失效的。其实cocoa程序设计中代理(delegate)这个概念和category联系时非常紧密的,delegates在cocoa编程后面的讲解中自然就知道是什么了,这里不在赘述了。在appkit中一些控件的使用经常是系统定义了一些category但是不实现它,等到用户使用的时候让用户实现。例如tableview控件中的- (BOOL) tableView: (NSTableView *) tableView shouldSelectRow: (int) row;方法就是这样实现的。
下面就来介绍下enumeration的用法,enumeration就是java中常用的迭代器。只不过它比迭代器的语法更简洁。例如NSArray实现了NSEnumeration这个协议就可以用下面的语法迭代集合中的元素。
NSArray *array = [NSArray arrayWithObjects:
@"One",@"Two",@"Three",@"Four",nil];
for (Nsstring *element in array)
{
NSLog(@"element: %@",element);
}
这里你不必指定集合遍历的终止条件,也不用指定下标。这种实现比迭代器用起来方便多了吧,同样NSArray遍历也可以用NSEnumeration来实现。
NSArray *array = [NSArray arrayWithObjects:
@"One",nil];
NSEnumerator *enumerator = [array reverSEObjectEnumerator];
for (Nsstring *element in enumerator)
{
if ([element isEqualToString:@"Three"])
{
break;
}
}
任何集合想要实现上面的用法,前提是要实现NSFastEnumeteration这个协议。下面我们就拿一个自己定义的集合例子来实现上面的方法。
#import <Foundation/Foundation.h>
#import <vector>
@interface MyFastEnumerationSample : NSObject<NSFastEnumeration>
{
std::vector<NSInteger> list;
}
-(id)initWithCapacity:(NSUInteger)numItems;
@end
@implementation MyFastEnumerationSample
// 初始化这个list
// 生成一些随机数,插入到list中
// 之后让枚举类返回
-(id)initWithCapacity:(NSUInteger)numItems
{
self = [super init];
if(self != nil)
{
for(NSInteger i = 0; i < numItems; ++i)
{
list.push_back(random());
}
}
return self;
}
// 这个方法就是fastEnumeration协议中的方法,这里你要实现它
// 实现这个方法,你有两种选择
// 1) 使用stackbuf,它是基于array的堆栈。如果用到了这个堆栈,那么你就必需处理下一个参数len的大小。
// 2) 返回你自己的array对象.如果你遍历完了这个array的所有对象,然后就返回0。例如:一个array的链表的实现需要返回每一个array,知道你遍历完所有的array。
// 无论上面那种实现方法,state->itemsPtr 必须要赋予一个合法的array(非空)。这个例子采用了方案1,使用stackbuf来存储结果。
-(NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len
{
NSUInteger count = 0;
// 下面是初始化,仅仅只会做一次。
// 确保你从来没有设置state->state为0,或者利用了其他的方法初始化它。
// (下面是使用state->extra中的一个值)。
if(state->state == 0)
{
// 我们不会跟踪变化,因此我们将设置state->mutationsPtr指向state->extra中的一个数值。
// 因为这些extra的数值并没有在协议里其他额外的地方使用。
// If your class was mutable,you may choose to use an internal variable that is updated when the class is mutated.假如你定义的类是可变的,你可能选择使用internal的变量,这个变量可以随着你的类大小的改变而改变。
// state->mutationsPtr 不能为空。
state->mutationsPtr = &state->extra[0];
}
//现在我们提供items,用来跟踪state->state和决定是否我们已经完成了迭代。
if(state->state < list.size())
{
// 这里需要设置state->itemsPtr指向提供的buffer。
// 如果需要不停的迭代来实现,那么就可能设置 state->itemsPtr指向一个内部的c数组对象。
// state->itemsPtr不能为空。
state->itemsPtr = stackbuf;
// 把我们list中提供的所有的items都填充到这个堆栈数组中。
// 假如我们提供的items太多,这个buffer也仅仅装得下len多的东西。后面的就会丢弃掉
while((state->state < list.size()) && (count < len))
{
// 这个例子在运行中生成内容。
// 一个真正的实现仅仅会从内部的容器中copy对象。
stackbuf[count] = [Nsstring stringWithFormat:@"Item %i = %i",state->state,list[state->state]];
state->state++;
count++;
}
}
else
{
// 我们已经包含了所有的items,因此return 0表明已经做完了。
count = 0;
}
return count;
}
@end
int main (int argc,const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// 为了演示,创建一个枚举类的实例,并且枚举里面所有的内容。
srandomdev();
MyFastEnumerationSample *example = [[MyFastEnumerationSample alloc] initWithCapacity:50];
for(id item in example)
{
NSLog(@"%@",item);
}
[pool drain];
return 0;
}
好了,这里你的一个fastenumeration就实现了。其实NSNumeration已经实现了NSFastenumeration这个协议。所以假如有一个自己实现的容器,就可以为这个容器定义一个Numeration,它只要继承 NSNumeration,就可以方便的实现 fastenumeration。想想fastNumeration多么方便你就再也不想用什么数组遍历了。