iOS设计模式之迭代器模式下
迭代器模式
在遍历的同时增删集合元素会发生什么?
在通过迭代器来遍历集合元素的同时,增加或者删除集合中的元素,有可能会导致某个元素被重复遍历或遍历不到。不过,并不是所有情况下都会遍历出错,有的时候也可以正常遍历,所以,这种行为称为结果不可预期行为或者未决行为,也就是说,运行结果到底是对还是错,要视情况而定。
@interface DMIteratorDesignPatterDemo : NSObject
@end
@implementation DMIteratorDesignPatterDemo
- (void)test
{
DMMutableArray *tmpArray = [[DMMutableArray alloc] init];
[tmpArray addObject:@"123"];
[tmpArray addObject:@"456"];
[tmpArray addObject:@"789"];
DMMutableArrayIterator *iterator = [tmpArray iterator];
[iterator next];
[tmpArray removeObjectAtIndex:0];
}
@end
在执行完DMMutableArrayIterator *iterator = [tmpArray iterator]; 时,数组中存储的是"123"、"456"、"789"三个元素,迭代器的游标mCursor指向元素"123"。当执行完[iterator next]; 的时候,游标mCursor指向元素"456",到这里都没有问题。
为了保持数组存储数据的连续性,数组的删除操作会涉及元素的搬移。当执行到[tmpArray removeObjectAtIndex:0]; 的时候,会从数组中将元素"123"删除掉,"456"、"789"两个元素会依次往前搬移一位,这就会导致游标mCursor本来指向元素"456",现在变成了指向元素"789"。原本在执行完[iterator next]; 代码之后,还可以遍历到"456"、"789"两个元素,但在执行完[tmpArray removeObjectAtIndex:0]; 删除代码之后,只能遍历到"789"一个元素了,"456"遍历不到了。
不过,如果代码删除的不是游标mCursor前面的元素以及游标mCursor所在位置的元素,而是游标mCursor后面的元素,这样就不会存在问题了,不会存在某个元素遍历不到的情况了。
所以,在遍历的过程中删除集合元素,结果是不可预期的,有时候没问题,有时候就有问题,这个要视情况而定。
在遍历的过程中删除集合元素,有可能会导致某个元素遍历不到,那在遍历的过程中添加集合元素,会发生什么情况呢?
@interface DMIteratorDesignPatterDemo : NSObject
@end
@implementation DMIteratorDesignPatterDemo
- (void)test
{
DMMutableArray *tmpArray = [[DMMutableArray alloc] init];
[tmpArray addObject:@"123"];
[tmpArray addObject:@"456"];
[tmpArray addObject:@"789"];
DMMutableArrayIterator *iterator = [tmpArray iterator];
[iterator next];
[tmpArray insertObject:@"abc" atIndex:0];
}
@end
在执行完DMMutableArrayIterator *iterator = [tmpArray iterator]; 之后,数组中包含"123"、"456"、"789"三个元素,迭代器的游标mCursor指向元素"123"。当执行完[iterator next]; 的时候,游标mCursor指向元素"456",在执行完[tmpArray insertObject:@"abc" atIndex:0]; 之后,将"abc"插入到下标为0的位置,"123"、"456"、"789"三个元素依次往后移动一位。这个时候,游标mCursor又指向元素"123"。元素"123"被游标重复指向两次,也就是元素"123"存在被重复遍历的情况。
跟删除情况类似,如果在游标的后面添加元素,就不会存在任何问题。所以,在遍历的同时添加集合元素也是一种不可预期行为。
如何应对遍历时改变集合导致的未决行为?
当通过迭代器来遍历集合的时候,增加、删除集合元素会导致不可预期的遍历结果。实际上,”不可预期“比直接出错更可拍,有的时候运行正确,有的时候运行错误,一些隐藏很深、很难debug的bug就是这么产生的。那如何才能避免出现这种不可预期的运行结果呢?
有两种比较干脆利索的解决方案:一种是遍历的时候不允许增删元素,另一种是增删元素之后让遍历报错。
实际上,第一种解决方案比较难实现,要确定遍历开始和结束的时间点。遍历开始的时间节点很容易获得。可以把创建迭代器的时间点作为遍历开始的时间点。但是,遍历结束的时间点很难确定。遍历到最后一个元素的时候结算结束?但是在实际的软件开发中,每次使用迭代器来遍历元素,并不一定非要把所有元素都遍历一遍。在迭代器类中定义一个新的方法 - (void)finishIteration 主动告诉容器迭代器使用完了,可以增删元素了,但是这要求程序员在使用迭代器之后要主动调用这个函数,增加了开发成本,还容易漏掉。
实际上,第二种解决方法更加合理。增删元素之后,让遍历报错。
怎么确定在遍历时候,集合有没有增删元素呢?在DMMutableArray中定义一个成员变量modCount,记录集合被修改的次数,集合每调用依次增加或者删除元素的方法,就会给modCount加1.当通过调用集合上的 - (DMMutableArrayIterator *)iterator 方法来创建迭代器的时候,会把modCount值传递给迭代器的expectedModCount成员变量,之后每次调用迭代器上的- (BOOL)hasNext 、- (void)next 、- (id)currentItem 方法,都会检查集合上的modCount是否等于expectedModCount,也就是看,在创建完迭代器之后,modCount是否改变过。
如果两个值不相同,那就说明集合存储的元素已经改变了,要么增加了元素,要么删除了元素,之前创建的迭代器已经不能正确运行了,再继续使用就会产生不可预期的结果,直接抛出异常。
@class DMMutableArrayIterator;
@interface DMMutableArray<ObjectType> : NSObject
@property (nonatomic, assign, readonly) NSUInteger count;
- (NSInteger)getModCount;
- (void)addObject:(ObjectType)anObject;
- (instancetype)init;
- (ObjectType)objectAtIndex:(NSUInteger)index;
- (void)insertObject:(ObjectType)anObject atIndex:(NSUInteger)index;
- (void)removeLastObject;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(ObjectType)anObject;
- (DMMutableArrayIterator *)iterator;
@end
@interface DMMutableArray ()
@property (nonatomic, strong) NSMutableArray *mArray;
@property (nonatomic, assign) NSInteger modCount;
@end
@implementation DMMutableArray
- (instancetype)init
{
if (self = [super init]) {
}
return self;
}
- (id)objectAtIndex:(NSUInteger)index
{
return [self.mArray objectAtIndex:index];
}
- (void)addObject:(id)anObject
{
self.modCount += 1;
[self.mArray addObject:anObject];
}
- (NSUInteger)count
{
return [self.mArray count];
}
- (NSInteger)getModCount
{
return self.modCount;
}
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index
{
self.modCount += 1;
[self.mArray insertObject:anObject atIndex:index];
}
- (void)removeLastObject
{
self.modCount += 1;
[self.mArray removeLastObject];
}
- (void)removeObjectAtIndex:(NSUInteger)index
{
self.modCount += 1;
[self.mArray removeObjectAtIndex:index];
}
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject
{
self.modCount += 1;
[self.mArray replaceObjectAtIndex:index withObject:anObject];
}
- (NSMutableArray *)mArray
{
if (_mArray == nil) {
_mArray = [[NSMutableArray alloc] init];
}
return _mArray;
}
- (DMMutableArrayIterator *)iterator
{
return [[DMMutableArrayIterator alloc] initWithDMMutableArray:self];
}
@end
@interface DMMutableArrayIterator<ObjectType> : NSObject
- (instancetype)initWithDMMutableArray:(DMMutableArray<ObjectType> *)array;
- (BOOL)hasNext;
- (void)next;
- (ObjectType)currentItem;
@end
@interface DMMutableArrayIterator ()
@property (nonatomic, assign) NSInteger mCursor;
@property (nonatomic, strong) DMMutableArray *mArray;
@property (nonatomic, assign) NSInteger expectedModCount;
@end
@implementation DMMutableArrayIterator
- (instancetype)initWithDMMutableArray:(DMMutableArray *)array
{
if (self = [super init]) {
self.mCursor = 0;
self.mArray = array;
self.expectedModCount = [array getModCount];
}
return self;
}
- (BOOL)hasNext
{
[self checkForComodification];
BOOL bFlag = YES;
if (self.mCursor >= [self.mArray count]) {
bFlag = NO;
}
return bFlag;
}
- (void)next
{
[self checkForComodification];
self.mCursor += 1;
}
- (id)currentItem
{
[self checkForComodification];
return [self.mArray objectAtIndex:self.mCursor];
}
- (void)checkForComodification
{
if (self.expectedModCount != [self.mArray getModCount]) {
@throw [NSException exceptionWithName:@"DMMutableArrayIteratorException" reason:@"集合元素改动了" userInfo:nil];
}
}
@end