有點(diǎn)經(jīng)驗(yàn)的老司機(jī)一看標(biāo)題就應(yīng)該知道我想說(shuō)什么,并可能給出一個(gè)謎之鄙視的表情??,所以你要是看完文章才知道這回事的話(huà)得回去補(bǔ)補(bǔ)數(shù)據(jù)結(jié)構(gòu)了。
在學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)的時(shí)候,我們就知道遍歷鏈表同時(shí)刪除元素的潛在隱患,只是長(zhǎng)時(shí)間不去寫(xiě)這種代碼可能會(huì)犯錯(cuò)誤。
開(kāi)門(mén)見(jiàn)山,現(xiàn)在有這樣一個(gè)數(shù)組:
NSMutableArray *mutArr = [[NSMutableArray alloc] initWithObjects:@1,@"a",@2,@"b",@3,@"c", nil];
如果讓你刪除其中的字符串,你會(huì)怎么做?
于是乎,我們咔咔就是寫(xiě):
for (id data in mutArr) {
if ([data isKindOfClass:[NSString class]]) {
[mutArr removeObject:data];
}
}
想必也是極快的!然而 crash了!
就是因?yàn)槟阌胒orin在遍歷的同時(shí)刪除了元素,數(shù)組規(guī)定在forin遍歷的時(shí)候不能修改數(shù)組元素。因?yàn)閯h除一個(gè)元素,沒(méi)有遍歷到的元素就會(huì)向前移動(dòng)一位,那迭代器就不知道接下來(lái)要遍歷當(dāng)前刪除位置的元素還是下一個(gè)位置的元素了。
但是有一種特殊情況,就是在刪除數(shù)組最后一個(gè)元素的時(shí)候可以使用forin,因?yàn)榈阶詈笠粋€(gè)元素的時(shí)候forin枚舉已經(jīng)結(jié)束了,這時(shí)候刪除元素不會(huì)影響到forin工作。
那么還有哪些方法可以正確做到遍歷數(shù)組的同時(shí)刪除元素呢?
1 用for循環(huán)
for (int i = 0; i < [mutArr count]; i ++) {
id data = [mutArr objectAtIndex:i];
if ([data isKindOfClass:[NSString class]]) {
[mutArr removeObject:data];
}
}
這樣是沒(méi)問(wèn)題的,因?yàn)槲覀兠看斡胕ndex來(lái)取值,總是可以按照正確的順序取到對(duì)應(yīng)的值的。
這里要注意的是:for循環(huán)的條件是i < [mutArr count]
, 而不能在for之前先計(jì)算好大小cnt
再用i < cnt
,這樣也會(huì)crash的。每刪除元素,數(shù)組都是變化的,其大小也是變化的。
2 用copy數(shù)組
NSMutableArray *copyArr = [mutArr mutableCopy];
for (id data in copyArr) {
if ([data isKindOfClass:[NSString class]]) {
[mutArr removeObject:data];
}
}
用forin遍歷copy出來(lái)的數(shù)組,在原來(lái)的數(shù)組做刪除操作,是完全可以的。很明顯,這種方式會(huì)有內(nèi)存消耗。
3 反向迭代
NSEnumerator *enumerator = [mutArr reverseObjectEnumerator];
for (id data in enumerator) {
if ([data isKindOfClass:[NSString class]]) {
[mutArr removeObject:data];
}
}
前面說(shuō)的是正向forin迭代遍歷時(shí)會(huì)crash,但反向就沒(méi)有問(wèn)題。因?yàn)榉聪虻鷷r(shí),沒(méi)有遍歷到的元素是不會(huì)移動(dòng)位置的,所以迭代器仍然可以正常工作。
4 predicate
[mutArr filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
NSLog(@"count == %ld",[mutArr count]);
if ([evaluatedObject isKindOfClass:[NSString class]]) {
return NO;
}
return YES;
}]];
這種方式也是可以過(guò)濾掉數(shù)據(jù)的。不過(guò)每次打印[mutArr count]
值是不變的,所以其工作原理很可能是做了幾次copy:
- NSPredicate復(fù)制原數(shù)組到一個(gè)新的集合,假定稱(chēng)為集合CopyA
- 通過(guò)Block過(guò)濾器過(guò)濾出集合CopyA中的元素元素到另一個(gè)數(shù)組集合,假定這個(gè)集合為集合CopyB
- 返回過(guò)濾數(shù)組集合CopyB,釋放集合CopyA,完成賦值到初始集合
可見(jiàn)這種方式是很耗內(nèi)存的,如果元素都是數(shù)據(jù)量比較大的如圖片,就可能出現(xiàn)OOM。
5 用While
int i = 0;
while ([mutArr count] > i) {
id data = [mutArr objectAtIndex:i];
if ([data isKindOfClass:[NSString class]]) {
[mutArr removeObject:data];
data = nil;//釋放元素
} else {
i++;
}
}
這種方式類(lèi)似于第一種for循環(huán),我們還可以及時(shí)釋放刪除的元素。
6 自定義Iterator
既然系統(tǒng)的迭代器有隱患,我們完全可以按照自己的想法來(lái)自定義一個(gè)迭代器。
具體demo可以參考這里
當(dāng)然,除了上面幾種方法,還有其他的遍歷方法可以做到,只是要注意特殊處理。關(guān)于數(shù)組的幾種遍歷方法,可以參考這篇文章,里面詳細(xì)比較了幾種遍歷方法的性能,并給了統(tǒng)計(jì)圖。先不論他的統(tǒng)計(jì)方法是否通用,我們還是可以從中大致了解各個(gè)遍歷方法的性能。另外,文章還順便分析了并行遍歷和數(shù)組分配的性能問(wèn)題,值得推薦!