iOS设计模式之迭代器模式下

2024-06-30  本文已影响0人  点滴86

迭代器模式

在遍历的同时增删集合元素会发生什么?

在通过迭代器来遍历集合元素的同时,增加或者删除集合中的元素,有可能会导致某个元素被重复遍历或遍历不到。不过,并不是所有情况下都会遍历出错,有的时候也可以正常遍历,所以,这种行为称为结果不可预期行为或者未决行为,也就是说,运行结果到底是对还是错,要视情况而定。

@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
上一篇下一篇

猜你喜欢

热点阅读