遍歷刪除NSMutableArray的那些事

有點(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了!

forin 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:

  1. NSPredicate復(fù)制原數(shù)組到一個(gè)新的集合,假定稱(chēng)為集合CopyA
  1. 通過(guò)Block過(guò)濾器過(guò)濾出集合CopyA中的元素元素到另一個(gè)數(shù)組集合,假定這個(gè)集合為集合CopyB
  2. 返回過(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)題,值得推薦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容