开发中我们会遇到这样一种需求,在遍历可变集合时,移除集合内指定类别的元素。比如,移除所有Null类别的元素等。一般而言思路有两种,一种是双集合模式,另一种是单集合模式。双集合模式也叫副本模式,具体思路为,区分原元素集合及移除元素集合,匹配移除。一般有两种实现方式,以数组为例:
第一种,遍历原元素数组,将移除元素加入移除元素数组,最后统一从原元素数组移除:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 原元素集合
NSMutableArray *array = @[@"1", @"11", @"111", [NSNull null], @"1111"].mutableCopy;
// 移除元素集合
NSMutableArray *removedArray = [NSMutableArray array];
for (id element in array)
{
if ([element isKindOfClass:[NSString class]])
{
[removedArray addObject:element];
}
}
[array removeObjectsInArray:removedArray];
NSLog(@"%@", array);
第二种与第一种类似,简化了些操作,定义一个副本,遍历副本找到匹配的元素,从移除元素数组中移除对应的元素:1
2
3
4
5
6
7
8
9
10
11
12
13
14// 移除元素集合
NSMutableArray *array = @[@"1", @"11", @"111", [NSNull null], @"1111"].mutableCopy;
// 原元素集合(副本)
NSMutableArray *copyArray = [NSMutableArray arrayWithArray:array];
for (id element in copyArray)
{
if ([element isKindOfClass:[NSString class]])
{
[array removeObject:element];
}
}
NSLog(@"%@", array);
双集合模式容易实现,也比较简单。但是由于额外创建了个集合副本,若集合比较大,会占用一定的内存空间。若不想使用双集合,我们也可以采用第二种思路,单集合模式,遍历单一集合的同时将指定元素直接从集合内移除。具体而言有三种实现方法,第一种是用for
实现,以数组为例:1
2
3
4
5
6
7
8
9
10
11
12
13NSMutableArray *array = @[@"1", @"11", @"111", [NSNull null], @"1111"].mutableCopy;
for (NSUInteger i = array.count; i > 0; i--)
{
id element = array[i-1];
if ([element isKindOfClass:[NSString class]])
{
[array removeObject:element];
}
}
NSLog(@"%@", array);
这里,我们使用for
遍历数组,并通过逆序将指定类别的元素移除出数组。而不使用正序移除原因在于,数组是有序集合,for
遍历过程中,随着元素的移除,所移除元素之后的其他元素对应的数组下标也会同步前移,移除目标与下标不一致,导致结果与预期相反。以下为使用正序移除:1
2
3
4
5
6
7
8
9
10
11
12
13NSMutableArray *array = @[@"1", @"11", @"111", [NSNull null], @"1111"].mutableCopy;
for (NSUInteger i = 0; i < array.count; i++)
{
id element = array[i];
if ([element isKindOfClass:[NSString class]])
{
[array removeObject:element];
}
}
NSLog(@"%@", array);
移除结果:1
2
3
4
5
6
7
8
9(
11,
"<null>"
)
原数组元素 -> @"1", @"11", @"111", [NSNull null], @"1111"
移除[0]后 -> @"11", @"111", [NSNull null], @"1111"
移除[1]后 -> @"11", [NSNull null], @"1111"
移除[2]后 -> @"11", [NSNull null]
这里,数组并未越界,估计当可变数组移除元素后,内部实现中的count也是同步改变,提前结束循环。而逆序移除中,元素移除后,下标改变的是遍历过的元素,没有遍历的元素下标并未改变,不会出现这种情况。因而,有序集合应该尽量通过逆序遍历来移除元素。使用for
进行遍历虽然比较简单,但缺点在于比较耗时,数据量比较大时,可以选择用forin
快速遍历实现。
直接使用forin
遍历数组的同时,移除元素,系统会抛出异常:1
Collection was mutated while being enumerated.
因而我们应当使用NSEnumerator
这个枚举器实现forin
逆序遍历,移除指定类别元素:1
NSEnumerator is an abstract class, instances of whose subclasses enumerate collections of other objects, such as arrays and dictionaries.
具体实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13NSMutableArray *array = @[@"1", @"11", @"111", [NSNull null], @"1111"].mutableCopy;
NSEnumerator *enumerator = [array reverseObjectEnumerator];
for (id element in enumerator)
{
if ([element isKindOfClass:[NSString class]])
{
[array removeObject:element];
}
}
NSLog(@"%@", array);
使用forin
进行遍历效率比较高,但是在遍历的过程中,我们无法直接获取元素的下标也无法直接修改被遍历的集合。若想保持遍历的高效,同时能方便获取集合内的每个元素,我们可以选择使用第三种实现方式,使用enumerateObjectsWithOptions:usingBlock:
等遍历方法实现,在Block
回调内将元素移除出数组:1
2
3
4
5
6
7
8
9
10
11
12
13NSMutableArray *array = @[@"1", @"11", @"111", [NSNull null], @"1111"].mutableCopy;
__weak typeof(array) weakArray = array; // retain cycle
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[NSString class]])
{
[weakArray removeObject:obj];
}
}];
NSLog(@"%@", array);
除了数组以外,我们也可在其他常用集合上实现:
字典:1
2
3
4
5
6
7
8
9
10
11
12
13NSMutableDictionary *dict = @{@"1":@"A", @"2":[NSNull null], @"3":@"B"}.mutableCopy;
__weak typeof(dict) weakDict = dict; // retain cycle
[dict enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[NSString class]])
{
[weakDict removeObjectForKey:key];
}
}];
NSLog(@"%@", dict);
Set:1
2
3
4
5
6
7
8
9
10
11
12
13NSMutableSet *set = [NSMutableSet setWithObjects:@"1", [NSNull null], @"2", nil];
__weak typeof(set) weakSet = set; // retain cycle
[set enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[NSString class]])
{
[weakSet removeObject:obj];
}
}];
NSLog(@"%@", set);
至此,似乎已经完成需求。然而,以上方法都只是对集合根元素进行类别判断移除操作,并未考虑集合根元素为集合的情况(如数组的元素为数组),此时我们也需对其子元素进行类别判断移除操作。具体实现上,这里采用单集合思路,若根元素为移除类别,则直接移除;若根元素为集合时,先通过递归对根元素集合进行判断移除操作后,获取新的根元素集合,若新的根元素集合不为空则替换,否则移除。同时,我们仅考虑数组、字典及Set等常用集合(其他集合可自行添加实现),通过集合分类方法的形式实现,具体如下:
可变数组:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54// 替换或移除遍历的元素(若obj为nil则移除)
- (void)replaceEnumerateObjectAtIndex:(NSUInteger)index withObject:(id)obj
{
if (!obj)
{
[self removeObject:obj];
return;
}
[self replaceObjectAtIndex:index withObject:obj];
}
// 移除数组及数组元素内所有特定类别的元素
- (void)removeObjectsFromClass:(Class)aClass
{
if (!self || [self isKindOfClass:[NSNull class]]) return;
if (self.count == 0) return;
__weak typeof(self) weakSelf = self;
[self enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj==nil || [obj isKindOfClass:aClass])
{
[weakSelf removeObject:obj];
return;
}
if ([obj isKindOfClass:[NSDictionary class]])
{
obj = [obj dictionaryByFilteringOutObjectsFromClass:aClass];
[weakSelf replaceEnumerateObjectAtIndex:idx withObject:obj];
return;
}
if ([obj isKindOfClass:[NSArray class]])
{
obj = [obj arrayByFilteringOutObjectsFromClass:aClass];
[weakSelf replaceEnumerateObjectAtIndex:idx withObject:obj];
return;
}
if ([obj isKindOfClass:[NSSet class]])
{
obj = [obj setByFilteringOutObjectsFromClass:aClass];
[weakSelf replaceEnumerateObjectAtIndex:idx withObject:obj];
return;
}
}];
}
数组:1
2
3
4
5
6
7
8
9
10
11
12
13// 获取移除特定类别元素后的新数组
- (NSArray *)arrayByFilteringOutObjectsFromClass:(Class)aClass
{
if (!self || [self isKindOfClass:[NSNull class]]) return nil;
if (self.count == 0) return self;
NSMutableArray *filteredArray = [NSMutableArray arrayWithArray:self];
[filteredArray removeObjectsFromClass:aClass];
return [filteredArray copy];
}
可变字典:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74// 替换或移除遍历的Key对应的元素(若obj为nil则移除)
- (void)replaceEnumerateObject:(id)object forKey:(NSString *)key
{
if (!key) return;
if (![[self allKeys] containsObject:key]) return;
if (!object)
{
[self removeObjectForKey:key];
return;
}
[self setObject:object forKey:key];
}
// 移除遍历的Key对应的特定类别的元素(若obj为nil则移除,否则替换Key对应的元素)
- (void)removeEnumerateObject:(id)obj fromClass:(Class)aClass forKey:(NSString *)key
{
if (!obj || [obj isKindOfClass:aClass])
{
[self removeObjectForKey:key];
return;
}
if ([obj isKindOfClass:[NSDictionary class]])
{
obj = [obj dictionaryByFilteringOutObjectsFromClass:aClass];
[self replaceEnumerateObject:obj forKey:key];
return;
}
if ([obj isKindOfClass:[NSArray class]])
{
obj = [obj arrayByFilteringOutObjectsFromClass:aClass];
[self replaceEnumerateObject:obj forKey:key];
return;
}
if ([obj isKindOfClass:[NSSet class]])
{
obj = [obj setByFilteringOutObjectsFromClass:aClass];
[self replaceEnumerateObject:obj forKey:key];
return;
}
}
// 移除字典内Key对应的特定类别的元素
- (void)removeObjectsFromClass:(Class)aClass forKey:(NSString *)key
{
if (!key) return;
if (![[self allKeys] containsObject:key]) return;
if (self == nil || [self isKindOfClass:[NSNull class]]) return;
id obj = [self objectForKey:key];
[self removeEnumerateObject:obj fromClass:aClass forKey:key];
}
// 移除字典内所有Keys对应的特定类别的元素
- (void)removeObjectsFromClass:(Class)aClass
{
if (self == nil || [self isKindOfClass:[NSNull class]]) return;
__weak typeof(self) weakSelf = self;
[self enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
[weakSelf removeEnumerateObject:obj fromClass:aClass forKey:key];
}];
}
字典:1
2
3
4
5
6
7
8
9
10
11
12// 获取移除所有Keys对应的特定类别元素的新字典
- (NSDictionary *)dictionaryByFilteringOutObjectsFromClass:(Class)aClass
{
if (!self || [self isKindOfClass:[NSNull class]]) return nil;
if (![self allKeys].count) return self;
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithDictionary:self];
[dictionary removeObjectsFromClass:aClass];
return [dictionary copy];
}
可变Set:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54// 替换或移除遍历的元素(若otherObj为nil则移除)
- (void)replaceEenumerateObject:(id)obj withObject:(id)otherObj
{
[self removeObject:obj];
if (otherObj)
{
[self addObject:otherObj];
}
}
// 移除Set内所有特定类别的元素
- (void)removeObjectsFromClass:(Class)aClass
{
if (!self || [self isKindOfClass:[NSNull class]]) return;
if (self.count == 0) return;
__weak typeof(self) weakSelf = self;
[self enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
if (obj == nil || [obj isKindOfClass:aClass])
{
[weakSelf removeObject:obj];
return;
}
id targetObj = nil;
if ([obj isKindOfClass:[NSDictionary class]])
{
targetObj = [obj dictionaryByFilteringOutObjectsFromClass:aClass];
[weakSelf replaceEenumerateObject:obj withObject:targetObj];
return;
}
if ([obj isKindOfClass:[NSArray class]])
{
targetObj = [obj arrayByFilteringOutObjectsFromClass:aClass];
[weakSelf replaceEenumerateObject:obj withObject:targetObj];
return;
}
if ([obj isKindOfClass:[NSSet class]])
{
targetObj = [obj setByFilteringOutObjectsFromClass:aClass];
[weakSelf replaceEenumerateObject:obj withObject:targetObj];
return;
}
}];
}
Set:1
2
3
4
5
6
7
8
9
10
11
12// 获取移除特定类别元素后的Set
- (NSSet *)setByFilteringOutObjectsFromClass:(Class)aClass
{
if (!self || [self isKindOfClass:[NSNull class]]) return nil;
if (self.count == 0) return self;
NSMutableSet *set = [NSMutableSet setWithSet:self];
[set removeObjectsFromClass:aClass];
return [set copy];
}
至此,基本实现完成,我们可移除集合内所有指定类别的元素:1
2
3
4
5
6
7
8
9
10NSMutableArray *arrayM = [NSMutableArray array];
arrayM[0] = @"0";
arrayM[1] = @[@"11", [NSNull null], @"12"];
arrayM[2] = @{@"A":@"21", @"B":@22, @"C":[NSNull null]};
arrayM[3] = [NSSet setWithObjects:[NSNull null], @"31", @"32", nil];
arrayM[4] = [NSNull null];
[arrayM removeObjectsFromClass:[NSString class]];
NSLog(@"%@", arrayM);
参考资料:
[1]张云龙.iOS开发遍历集合(NSArray,NSDictionary、NSSet)方法总结[EB/OL].http://www.jianshu.com/p/d6ef96c862ca ,2016-2-26.
[2]Zhangzhan_zg.遍历可变数组的同时删除数组元素的几种解决方案[EB/OL].http://blog.csdn.net/zhangzhan_zg/article/details/38453305 ,2014-8-14.
[3]Apple Inc.NSEnumerator Reference Class[EB/OL].https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSEnumerator_Class/index.html ,2016-7-27.