三 category和enumeration的用法


继承是面向对象程序设计的一个重要特性,但是继承的一些缺点也越来越多被人们意识到。因为继承有时候会破坏类的封装性,使子类可以使用父类的一些非pubic的方法。另外当继承树大到一定程度的时候相信许多程序员都不愿意看到,因为毕竟程序不仅仅是要让计算机运行的,更重要的一点就是要人能够看懂,否则这样的程序也只能束之高阁,供人膜拜了。根据研究表明继承的层次维持3层以下是最容易让人理解。

  所以继承有时候并不表现的那么有用,其实在设计模式中,适配器模式就可以解释用继承是多么的糟糕。那么不用继承objective c如何扩展一个类那,那么Apple的工程师就设计了category这个新语法特性。它的功能就是实现类的扩展而完全不用继承。例如我们要给NSString类增加一个新的功能计算NSString的长度,并且返回值为NSNumber。假如你要用继承实现


@interface NSNewString:NSString
-(NSNumber *)lengthAsNumber;
@end

-(NSNumber *)lengthAsNumber
{
unsigned int length = [self length];
return ([NSNumber numberWithUnsignedInt:length]);
}

现在我开始调用假如用下面的方法得到一个字符串

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", @"Two", @"Three", @"Four", 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多么方便你就再也不想用什么数组遍历了。

原文链接

如果你喜欢这篇文章,谢谢你的赞赏

图3

如有疑问请联系我