? ? ? 今日在項(xiàng)目開發(fā)中遇到一個(gè)問(wèn)題:在職位列表A中點(diǎn)擊職位進(jìn)入職位詳細(xì)頁(yè)面B愁茁,點(diǎn)擊申請(qǐng)職位會(huì)到申請(qǐng)職位頁(yè)面C蚕钦,申請(qǐng)成功會(huì)到申請(qǐng)成功頁(yè)面D,在D中又有了一個(gè)相似職位列表鹅很,點(diǎn)擊職位又可以進(jìn)入一個(gè)職位詳情頁(yè)面嘶居。。促煮。邮屁。。污茵。那么問(wèn)題來(lái)了樱报,如果不加限制葬项,那么會(huì)導(dǎo)致一個(gè)一個(gè)的新的B或者C或者D會(huì)被push進(jìn)來(lái)∨⒌保現(xiàn)在要把邏輯改為,點(diǎn)擊D的返回按鈕民珍,就直接返回的職位列表襟士,不再一級(jí)一級(jí)地返回。?
?????? 本來(lái)想在D中自定義返回按鈕嚷量,讓其返回的時(shí)候pop到指定頁(yè)面陋桂,但是因?yàn)轫?xiàng)目原因,無(wú)法知道是從具體那個(gè)頁(yè)面進(jìn)來(lái)的蝶溶,也就無(wú)法確定要pop到那個(gè)頁(yè)面了嗜历。那么就要操作self.navigationViewController的viewControllers數(shù)組了。我們需要在進(jìn)入D的時(shí)候?qū)⑵淝懊娴腂抖所、C一類的頁(yè)面從viewControllers中移除之后再將D加入進(jìn)來(lái)梨州。
?????? 最開始我習(xí)慣性地用了iOS的快速枚舉方法來(lái)視圖刪除B、C這類viewController田轧,但是程序在這個(gè)地方奔潰了暴匠,沒有給任何錯(cuò)誤提示,只有這么一句:NSScanner: nil string argument傻粘。
NSMutableArray *mutViewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
??? for(UIViewController *tmpVc in mutViewControllers)
??? {
??????? if([tmpVc isKindOfClass:[NSClassFromString(@"YLJobDetailViewController") class]]||[tmpVc isKindOfClass:[NSClassFromString(@"YLApplyJobViewController") class]])
??????? {
??????????? [mutViewControllers removeObject:tmpVc];
??????? }
??? }
后來(lái)嘗試了傳統(tǒng)的枚舉方法每窖,卻成功了,如下:
??? for(NSInteger i=0;i<mutViewControllers.count;i++)
??? {
??????? UIViewController *tmpVc = mutViewControllers[i];
??????? if([tmpVc isKindOfClass:[NSClassFromString(@"YLJobDetailViewController") class]]||[tmpVc isKindOfClass:[NSClassFromString(@"YLApplyJobViewController") class]]||[tmpVc isKindOfClass:[NSClassFromString(@"YLApplyForInterviewViewController") class]])
??????? {
??????????? [mutViewControllers removeObjectAtIndex:i];
??????????? i--;
??????? }
??? }
這是為什么呢弦悉?區(qū)別在哪里窒典?后面再討論這個(gè)問(wèn)題!
又因?yàn)閺腄返回到的頁(yè)面有的是要隱藏tabbar的稽莉,有些又不需要隱藏崇败,所以這個(gè)地方要加上判斷,所以具體實(shí)現(xiàn)如下:
NSMutableArray *mutViewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
??? for(NSInteger i=0;i<mutViewControllers.count;i++)
??? {
??????? UIViewController *tmpVc = mutViewControllers[i];
??????? if([tmpVc isKindOfClass:[NSClassFromString(@"YLJobDetailViewController") class]]||[tmpVc isKindOfClass:[NSClassFromString(@"YLApplyJobViewController") class]]||[tmpVc isKindOfClass:[NSClassFromString(@"YLApplyForInterviewViewController") class]])
??????? {
??????????? [mutViewControllers removeObjectAtIndex:i];
??????????? i--;
??????? }
??? }
??? YLApplyForInterviewViewController *applyInterViewVc = [[YLApplyForInterviewViewController alloc]initWithType:type];
??? if(type==RegisterSuccessTypeActivate){
??????? applyInterViewVc.userPhone = values[2];
??? }
? ? YLBaseViewController
*lastVc = (YLBaseViewController *)mutViewControllers.lastObject;
??? BOOL hideBottomBar = lastVc.hidesBottomBarWhenPushed;
??? if(hideBottomBar==NO)
??? {
? ? ? ? applyInterViewVc.hidesBottomBarWhenPushed = YES;
??????? [lastVc.navigationController pushViewController:applyInterViewVc animated:YES];
? ? }
??? else
??? {
??????? [lastVc.navigationController pushViewController:applyInterViewVc animated:YES];
??? }
??? [mutViewControllers addObject:applyInterViewVc];
??? [self.navigationController setViewControllers:mutViewControllers animated:YES];
出現(xiàn)上面的問(wèn)題主要是對(duì)數(shù)組的枚舉理解的不夠深刻。那么我們來(lái)看看iOS中的NSArray有哪些枚舉方法后室,它們有什么區(qū)別缩膝。
?================2015-10-10修改===============
上面的修改系統(tǒng)導(dǎo)航的viewcontrollers的例子不太合適,因?yàn)樵诤笃诘拈_發(fā)中發(fā)現(xiàn)這種通過(guò)修改系統(tǒng)導(dǎo)航的viewcontrollers的方式來(lái)改變導(dǎo)航返回到指定視圖控制器的方法在很多時(shí)候是容易出bug的岸霹。因?yàn)樵谙到y(tǒng)導(dǎo)航的push或者pop動(dòng)畫未結(jié)束前操作它的viewcontrollers很容易導(dǎo)致程序直接crash疾层,報(bào)錯(cuò)信息:
NSScanner:nil string argument
libc++abi.dylib: terminate_handler unexpectedly threw an exception
所以在后期開發(fā)中遇到這樣的需求還是不推薦這么做,而是在要修改返回按鈕事件的視圖控制器中自定義返回按鈕贡避,然后實(shí)現(xiàn)返回按鈕的點(diǎn)擊事件痛黎。
舉個(gè)栗子:
-(void)backAction
{
UIViewController*targetVc =nil;
if(self.targetPopControllerName){
for(NSIntegeri=self.navigationController.viewControllers.count-1;i>=0;i--){
UIViewController*tmpVc =self.navigationController.viewControllers[i];
if([tmpVcisKindOfClass:NSClassFromString(self.targetPopControllerName)]){
targetVc = tmpVc;
break;
}
}
}
if(targetVc==nil){
[self.navigationControllerpopViewControllerAnimated:YES];
}else{
[self.navigationControllerpopToViewController:targetVcanimated:YES];
}
}
=========================================
好了,下面進(jìn)入主題:
現(xiàn)有數(shù)組:
NSMutableArray *mutArray = [NSMutableArray arrayWithObjects:
??????????????????????????????? @"test",
??????????????????????????????? @"test1",
??????????????????????????????? @"test2",
??????????????????????????????? @"test3",
??????????????????????????????? @"test2",
??????????????????????????????? @"test",
??????????????????????????????? @"test1",
??????????????????????????????? @"test2",
??????????????????????????????? @"test3",
??????????????????????????????? @"test2",
??????????????????????????????? nil];
要將其中的@“test2”全部刪除刮吧,得到一個(gè)新的數(shù)組湖饱,你能想到幾種方法?杀捻?井厌?
//0.最簡(jiǎn)單的方式
[mutArray removeObject:@“test2"];
這種是最簡(jiǎn)單的方式,但是往往大家是想不起來(lái)的致讥,為什么呢仅仆,就是因?yàn)槲覀兒雎粤俗址膇sEqual方法是以它的內(nèi)容為準(zhǔn)的。NSString認(rèn)為垢袱,只要兩個(gè)字符串的description墓拜,也就是字符內(nèi)容相同,就認(rèn)為是相同的请契。(isEqual: 首先判斷兩個(gè)對(duì)象是否類型一致咳榜, 在判斷具體內(nèi)容是否一致,如果類型不同直接return no.如先判斷是否都是 NSString爽锥,在判斷string的內(nèi)容涌韩。
isEqualToString: 這個(gè)直接判斷字符串內(nèi)容,當(dāng)然你要確保比較的對(duì)象保證是字符串救恨。)而數(shù)組的removeObject方法是用isEqual去匹配的贸辈。再比如要?jiǎng)h除所有的@“test2”,@“test3”,可以用[mutArray removeObjectsInArray:@[@“test2”,@"test3"]];這種方法。
但是這種方法也就適合當(dāng)前這種情況肠槽,如果要?jiǎng)h除多種數(shù)據(jù)擎淤,就不行了。
//1.傳統(tǒng)的下標(biāo)方法 no pro秸仙!
??? for(NSInteger i=0;i<mutArray.count;i++)
??? {
??????? NSString *tmpStr = mutArray[i];
??????? if([tmpStr isEqualToString:@"test2"])
??????? {
??????????? [mutArray removeObjectAtIndex:i];
??????????? i--;
??????? }
??? }
這種大家應(yīng)該都能一下子想到嘴拢,并且這種方式是肯定沒有問(wèn)題的。但是當(dāng)數(shù)組元素很多的時(shí)候效率可能就沒那么高了寂纪。
——————————以下是快速枚舉方法——————
?
??? //2.快速枚舉的時(shí)候改變了可變數(shù)組? 可行嗎席吴????? 加上__strong如何赌结?
??? for(NSString *str in mutArray)
??? {
??????? if([str isEqualToString:@"test2"])
??????? {
??????????? [mutArray removeObject:str];
??????? }
??? }
這種快速枚舉方法呢?你會(huì)發(fā)現(xiàn)即使你加上__strong關(guān)鍵字孝冒,依然會(huì)報(bào)錯(cuò):Collection <__NSArrayM: 0x7fb6c0439fe0> was mutated while being enumerated
也就是說(shuō)可變數(shù)組在快速枚舉的時(shí)候不能修改其variables 的引用屬性柬姚。而我們?cè)谶@里做了remove操作,所以會(huì)異常庄涡。
即使改成下面這樣依然不行:
for(NSString *str in mutArray)
??? {
??????? NSLog(@"%@",str);
??????? if([str isEqualToString:@"test2"])
??????? {
?????????? NSInteger index = [mutArray indexOfObject:str];
??????????? [mutArray removeObjectAtIndex:index];
??????? }
??? }
這就是在可變數(shù)組快速枚舉的時(shí)候量承,不能對(duì)其做改變操作,因?yàn)檫@樣會(huì)造成索引錯(cuò)亂的現(xiàn)象穴店。再比如如果數(shù)組中全部是字符串的話撕捍,這種直接remove操作也會(huì)出現(xiàn)一下子將相同的字符串全部移除的誤操作。
?
注意:這種情況下泣洞,即使數(shù)組中只包含一個(gè)@“test2”也不可以忧风!
//3.在枚舉的時(shí)候枚舉[mutArray copy],而在移除的時(shí)候操作mutArray 將如何球凰?
??? for(NSString *str in [mutArray copy])
??? {
??????? if([str isEqualToString:@"test2"])
??????? {
??????????? [mutArray removeObject:str];
??????? }
??? }
事實(shí)證明這種方法是可行的狮腿,但是當(dāng)數(shù)組非常大,而需要?jiǎng)h除的數(shù)據(jù)又很少的時(shí)候呢弟蚀?這樣就會(huì)導(dǎo)致額外地開銷了很多內(nèi)存蚤霞。
//4.將要?jiǎng)h除的數(shù)據(jù)放進(jìn)一個(gè)新的數(shù)組中暫存
??? NSMutableArray *toDeleteArray = [NSMutableArray array];
??? for(NSString *str in mutArray)
??? {
??????? if([str isEqualToString:@"test2"])
??????? {
??????????? [toDeleteArray addObject:str];
??????? }
??? }
??? [mutArray removeObjectsInArray:toDeleteArray];
這樣的方式也可行酗失,但這相當(dāng)于要循環(huán)兩次了义钉,先循環(huán)一次得到要?jiǎng)h除的對(duì)象,然后removeObjectsInArray又相當(dāng)于一次循環(huán)刪除的操作了规肴。
//5.外部迭代——迭代器?? 這種可以嗎捶闸?
??? NSEnumerator *enumerator=[mutArray objectEnumerator];//得到一個(gè)mutArray的枚舉對(duì)象
??? NSString *str;
??? while (str = [enumerator nextObject]) {
??????? if([str isEqualToString:@"test2"])
??????? {
??????????? [mutArray removeObject:str];
??????????????????????????? NSLog(@"xxxx");
??????? }
??? }
這樣的方式呢?報(bào)錯(cuò):Collection <__NSArrayM: 0x7fe180d2ee10> was mutated while being enumerated.’
?
同樣是在枚舉的時(shí)候修改了數(shù)組中對(duì)象的引用屬性拖刃。但是你會(huì)發(fā)現(xiàn)在NSLog(@“xxxx”);處斷點(diǎn)删壮,當(dāng)程序走到這里的時(shí)候,一樣是全部刪除了@“test2”兑牡。這就造成了索引的錯(cuò)亂央碟。
注意:這個(gè)地方即使將數(shù)組修改為只包含一個(gè)@“test2”的數(shù)組,同樣是不行的均函!
NSEnumerator *enumerator=[mutArray reverseObjectEnumerator];//得到一個(gè)倒序的mutArray的枚舉對(duì)象
??? NSString *str;
??? while (str = [enumerator nextObject]) {
??????? NSLog(@"----%@",str);
??????? if([str isEqualToString:@"test2"])
??????? {
??????????? [mutArray removeObject:str];
??????????? NSLog(@"xxxx");
??????? }
??? }
這樣翻轉(zhuǎn)過(guò)來(lái)再迭代呢亿虽?會(huì)報(bào)數(shù)組越界錯(cuò)誤:-[__NSArrayM objectAtIndex:]: index 8 beyond bounds [0 .. 5]’
我們?cè)贜SLog(@“xxxx”);處斷點(diǎn)發(fā)現(xiàn),當(dāng)?shù)谝淮纬绦蜃叩綌帱c(diǎn)處的時(shí)候苞也,數(shù)組中所有的@“test2”全部已經(jīng)被刪除了洛勉,這個(gè)時(shí)候索引并沒有卻還是8,那就會(huì)導(dǎo)致越界如迟。
注意:這個(gè)地方如果將數(shù)組修改為只包含一個(gè)@“test2”的數(shù)組收毫,將運(yùn)行正常攻走。這是因?yàn)椴粫?huì)造成索引錯(cuò)亂。
NSEnumerator *enumerator=[mutArray reverseObjectEnumerator];//得到一個(gè)倒序的mutArray的枚舉對(duì)象
??? NSString *str;
??? while (str = [enumerator nextObject]) {
??????? NSLog(@"----%@",str);
??????? if([str isEqualToString:@"test2"])
??????? {
?????????? NSInteger index = [mutArray indexOfObject:str];
??????????? [mutArray removeObjectAtIndex:index];
??????????? NSLog(@"xxxx");
??????? }
??? }
這種方式是可以的此再。是按照索引一個(gè)個(gè)刪除的昔搂,而不是按照對(duì)象來(lái)刪除的。
——————————以下是塊枚舉——————
//6.塊枚舉( 并發(fā)枚舉)? 這種可以嗎输拇?
??? [mutArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
??????? NSString *str = (NSString *)obj;
??????? if([str isEqualToString:@"test2"]){
??????????? [mutArray removeObjectAtIndex:idx];
??????? }
??? }];
這種方式正確巩趁,刪除了數(shù)組中的所有@“test2”數(shù)據(jù)。在[mutArray removeObjectAtIndex:idx];處斷點(diǎn)淳附,可以看到程序走了四次议慰,并且idx是遞增的。
這個(gè)地方將[mutArray removeObjectAtIndex:idx];換成[mutArray removeObject:str];一樣是可以的奴曙。而且會(huì)發(fā)現(xiàn)換成這樣當(dāng)idx=2時(shí)將數(shù)組中的全部@“test2”刪除了别凹,當(dāng)idx=3時(shí),str對(duì)應(yīng)到了@”test”而不是@”test3”了洽糟,它跳過(guò)了@“test3”炉菲。idx到5就結(jié)束了。
這說(shuō)明在使用數(shù)組的removeObject方法時(shí)一定要注意數(shù)組中元素的isEqual方法坤溃!
//這樣可以嗎拍霜?
??? [mutArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
??????? NSString *str = (NSString *)obj;
??????? if([str isEqualToString:@"test2"]){
??????????? [mutArray removeObjectAtIndex:idx];
??????? }
??? }];
這種 NSEnumerationConcurrent 枚舉過(guò)程中,各個(gè)Block是同時(shí)開始執(zhí)行的薪介。這樣枚舉的完成順序是不確定的祠饺。在NSString *str = (NSString *)obj;處斷點(diǎn)可以idx是不按照順序來(lái)的,也會(huì)報(bào)數(shù)組越界的錯(cuò)誤汁政。這就是順序不確定造成的道偷。
?
但是注意:這個(gè)地方換成[mutArray removeObject:str];反而是可以的。
?//這樣可以嗎记劈?
??? [mutArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
??????? NSString *str = (NSString *)obj;
??????? if([str isEqualToString:@"test2"]){
??????????? [mutArray removeObjectAtIndex:idx];
??????? }
??? }];
這種 NSEnumerationReverse以反序方式枚舉勺鸦。在NSString *str = (NSString *)obj;處斷點(diǎn)可以發(fā)現(xiàn),idx是從大到小遞減的目木,是按順序來(lái)的换途,不會(huì)報(bào)錯(cuò)。
這個(gè)地方換成[mutArray removeObject:str];一樣是可行的刽射,雖然當(dāng)idx=9的時(shí)候已經(jīng)將所有的@“test2”全部刪除军拟,但是當(dāng)idx=8時(shí),你會(huì)發(fā)現(xiàn)obj=nil柄冲,并沒有報(bào)數(shù)組越界的錯(cuò)誤吻谋。
?
使用并發(fā)枚舉需要注意:
如果情況允許,你可以選擇用塊枚舉來(lái)并發(fā)枚舉對(duì)象现横。這意味著計(jì)算的工作量可以分散到幾個(gè) CPU 內(nèi)核上漓拾。并不是每種枚舉過(guò)程中的處理都是可并發(fā)的阁最,因此只有沒用到鎖的時(shí)候,才能使用并發(fā)枚舉:要么每一步操作確實(shí)是絕對(duì)相互獨(dú)立的骇两,要么有原子性的操作可用速种。
總結(jié):
上面說(shuō)了那么多,說(shuō)實(shí)話我自己都有點(diǎn)繞的傻傻分不清了低千。配阵。。示血。棋傍。。其實(shí)從本文可以看到有幾個(gè)重點(diǎn)是需要注意的:
1.可變數(shù)組在快速枚舉的時(shí)候不要嘗試修改它难审。
2.需要注意字符串的isEqual:方法和isEqaulToString:方法的區(qū)別瘫拣。
3.在使用數(shù)組的removeObject:方法時(shí),你要先搞明白數(shù)組中元素的isEqaul:方法告喊,避免誤刪除麸拄。對(duì)于自定義類型,可以自定義isEqual:方法黔姜。
推薦閱讀:
1.http://stackoverflow.com/questions/8834031/objective-c-nsmutablearray-mutated-while-being-enumerated
推薦這篇文章:NSArray 枚舉性能研究
大家可以看看拢切,然后在實(shí)際項(xiàng)目中根據(jù)實(shí)際情況選擇使用合適的枚舉方法。
2.http://www.oschina.net/translate/nsarray-enumeration-performance