iOSNSArray枚举

      今日在项目开发中遇到一个问题:在职位列表A中点击职位进入职位详细页面B,点击申请职位会到申请职位页面C,申请成功会到申请成功页面D,在D中又有了一个相似职位列表,点击职位又可以进入一个职位详情页面。。。。。。那么问题来了,如果不加限制,那么会导致一个一个的新的B或者C或者D会被push进来。现在要把逻辑改为,点击D的返回按钮,就直接返回的职位列表,不再一级一级地返回。

       本来想在D中自定义返回按钮,让其返回的时候pop到指定页面,但是因为项目原因,无法知道是从具体那个页面进来的,也就无法确定要pop到那个页面了。那么就要操作self.navigationViewController的viewControllers数组了。我们需要在进入D的时候将其前面的B、C一类的页面从viewControllers中移除之后再将D加入进来。

 

       最开始我习惯性地用了iOS的快速枚举方法来视图删除B、C这类viewController,但是程序在这个地方奔溃了,没有给任何错误提示,只有这么一句: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];

        }

    }


后来尝试了传统的枚举方法,却成功了,如下:

    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--;

        }

    }

 

这是为什么呢?区别在哪里?后面再讨论这个问题!

 

 

又因为从D返回到的页面有的是要隐藏tabbar的,有些又不需要隐藏,所以这个地方要加上判断,所以具体实现如下:

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];

 

出现上面的问题主要是对数组的枚举理解的不够深刻。那么我们来看看iOS中的NSArray有哪些枚举方法,它们有什么区别。

 

好了,下面进入主题:

 

现有数组:

NSMutableArray *mutArray = [NSMutableArray arrayWithObjects:

                                @"test",

                                @"test1",

                                @"test2",

                                @"test3",

                                @"test2",

                                @"test",

                                @"test1",

                                @"test2",

                                @"test3",

                                @"test2",

                                nil];

要将其中的@“test2”全部删除,得到一个新的数组,你能想到几种方法???

 

//0.最简单的方式

[mutArray removeObject:@“test2"];

这种是最简单的方式,但是往往大家是想不起来的,为什么呢,就是因为我们忽略了字符串的isEqual方法是以它的内容为准的。NSString认为,只要两个字符串的description,也就是字符内容相同,就认为是相同的。(isEqual: 首先判断两个对象是否类型一致, 在判断具体内容是否一致,如果类型不同直接return no.如先判断是否都是 NSString,在判断string的内容。

isEqualToString: 这个直接判断字符串内容,当然你要确保比较的对象保证是字符串。)而数组的removeObject方法是用isEqual去匹配的。再比如要删除所有的@“test2”,@“test3”,可以用[mutArray removeObjectsInArray:@[@“test2”,@"test3"]];这种方法。

但是这种方法也就适合当前这种情况,如果要删除多种数据,就不行了。

 

 

//1.传统的下标方法 no pro!

    for(NSInteger i=0;i<mutArray.count;i++)

    {

        NSString *tmpStr = mutArray[i];

        if([tmpStr isEqualToString:@"test2"])

        {

            [mutArray removeObjectAtIndex:i];

            i--;

        }

    }

 

这种大家应该都能一下子想到,并且这种方式是肯定没有问题的。但是当数组元素很多的时候效率可能就没那么高了。

 

 

 

——————————以下是快速枚举方法——————

 

   

    //2.快速枚举的时候改变了可变数组  可行吗?     加上__strong如何?

    for(NSString *str in mutArray)

    {

        if([str isEqualToString:@"test2"])

        {

            [mutArray removeObject:str];

        }

    }

这种快速枚举方法呢?你会发现即使你加上__strong关键字,依然会报错:Collection <__NSArrayM: 0x7fb6c0439fe0> was mutated while being enumerated

也就是说可变数组在快速枚举的时候不能修改其variables 的引用属性。而我们在这里做了remove操作,所以会异常。

 

即使改成下面这样依然不行:

for(NSString *str in mutArray)

    {

        NSLog(@"%@",str);

        if([str isEqualToString:@"test2"])

        {

           NSInteger index = [mutArray indexOfObject:str];

            [mutArray removeObjectAtIndex:index];

        }

    }

这就是在可变数组快速枚举的时候,不能对其做改变操作,因为这样会造成索引错乱的现象。再比如如果数组中全部是字符串的话,这种直接remove操作也会出现一下子将相同的字符串全部移除的误操作。

 

注意:这种情况下,即使数组中只包含一个@“test2”也不可以!

 

//3.在枚举的时候枚举[mutArray copy],而在移除的时候操作mutArray 将如何?

    for(NSString *str in [mutArray copy])

    {

        if([str isEqualToString:@"test2"])

        {

            [mutArray removeObject:str];

        }

    }

 

事实证明这种方法是可行的,但是当数组非常大,而需要删除的数据又很少的时候呢?这样就会导致额外地开销了很多内存。

 

 

 

//4.将要删除的数据放进一个新的数组中暂存

    NSMutableArray *toDeleteArray = [NSMutableArray array];

    for(NSString *str in mutArray)

    {

        if([str isEqualToString:@"test2"])

        {

            [toDeleteArray addObject:str];

        }

    }

    [mutArray removeObjectsInArray:toDeleteArray];

这样的方式也可行,但这相当于要循环两次了,先循环一次得到要删除的对象,然后removeObjectsInArray又相当于一次循环删除的操作了。

 

 

 

//5.外部迭代——迭代器   这种可以吗?

    NSEnumerator *enumerator=[mutArray objectEnumerator];//得到一个mutArray的枚举对象

    NSString *str;

    while (str = [enumerator nextObject]) {

        if([str isEqualToString:@"test2"])

        {

            [mutArray removeObject:str];

                            NSLog(@"xxxx");

        }

    }

 

这样的方式呢?报错:Collection <__NSArrayM: 0x7fe180d2ee10> was mutated while being enumerated.’

 

同样是在枚举的时候修改了数组中对象的引用属性。但是你会发现在NSLog(@“xxxx”);处断点,当程序走到这里的时候,一样是全部删除了@“test2”。这就造成了索引的错乱。

注意:这个地方即使将数组修改为只包含一个@“test2”的数组,同样是不行的!

 

 

 

NSEnumerator *enumerator=[mutArray reverseObjectEnumerator];//得到一个倒序的mutArray的枚举对象

    NSString *str;

    while (str = [enumerator nextObject]) {

        NSLog(@"----%@",str);

        if([str isEqualToString:@"test2"])

        {

            [mutArray removeObject:str];

            NSLog(@"xxxx");

        }

    }

这样翻转过来再迭代呢?会报数组越界错误:-[__NSArrayM objectAtIndex:]: index 8 beyond bounds [0 .. 5]’

我们在NSLog(@“xxxx”);处断点发现,当第一次程序走到断点处的时候,数组中所有的@“test2”全部已经被删除了,这个时候索引并没有却还是8,那就会导致越界。

注意:这个地方如果将数组修改为只包含一个@“test2”的数组,将运行正常。这是因为不会造成索引错乱。

 

 

NSEnumerator *enumerator=[mutArray reverseObjectEnumerator];//得到一个倒序的mutArray的枚举对象

    NSString *str;

    while (str = [enumerator nextObject]) {

        NSLog(@"----%@",str);

       

        if([str isEqualToString:@"test2"])

        {

           NSInteger index = [mutArray indexOfObject:str];

            [mutArray removeObjectAtIndex:index];

            NSLog(@"xxxx");

        }

    }

这种方式是可以的。是按照索引一个个删除的,而不是按照对象来删除的。

 

 

 

 

——————————以下是块枚举——————

 

 

//6.块枚举( 并发枚举)  这种可以吗?

    [mutArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

        NSString *str = (NSString *)obj;

        if([str isEqualToString:@"test2"]){

            [mutArray removeObjectAtIndex:idx];

        }

    }];

这种方式正确,删除了数组中的所有@“test2”数据。在[mutArray removeObjectAtIndex:idx];处断点,可以看到程序走了四次,并且idx是递增的。

这个地方将[mutArray removeObjectAtIndex:idx];换成[mutArray removeObject:str];一样是可以的。而且会发现换成这样当idx=2时将数组中的全部@“test2”删除了,当idx=3时,str对应到了@”test”而不是@”test3”了,它跳过了@“test3”。idx到5就结束了。

这说明在使用数组的removeObject方法时一定要注意数组中元素的isEqual方法!

 

 

//这样可以吗?

    [mutArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

        NSString *str = (NSString *)obj;

        if([str isEqualToString:@"test2"]){

            [mutArray removeObjectAtIndex:idx];

        }

    }];

 

这种 NSEnumerationConcurrent 枚举过程中,各个Block是同时开始执行的。这样枚举的完成顺序是不确定的。在NSString *str = (NSString *)obj;处断点可以idx是不按照顺序来的,也会报数组越界的错误。这就是顺序不确定造成的。

 

但是注意:这个地方换成[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;处断点可以发现,idx是从大到小递减的,是按顺序来的,不会报错。

这个地方换成[mutArray removeObject:str];一样是可行的,虽然当idx=9的时候已经将所有的@“test2”全部删除,但是当idx=8时,你会发现obj=nil,并没有报数组越界的错误。

 

使用并发枚举需要注意:

如果情况允许,你可以选择用块枚举来并发枚举对象。这意味着计算的工作量可以分散到几个 CPU 内核上。并不是每种枚举过程中的处理都是可并发的,因此只有没用到锁的时候,才能使用并发枚举:要么每一步操作确实是绝对相互独立的,要么有原子性的操作可用。

 

 

总结:

上面说了那么多,说实话我自己都有点绕的傻傻分不清了。。。。。。其实从本文可以看到有几个重点是需要注意的:

1.可变数组在快速枚举的时候不要尝试修改它。

2.需要注意字符串的isEqual:方法和isEqaulToString:方法的区别。

3.在使用数组的removeObject:方法时,你要先搞明白数组中元素的isEqaul:方法,避免误删除。对于自定义类型,可以自定义isEqual:方法。

 

 

推荐阅读:

1.http://stackoverflow.com/questions/8834031/objective-c-nsmutablearray-mutated-while-being-enumerated

 

推荐这篇文章:NSArray 枚举性能研究

大家可以看看,然后在实际项目中根据实际情况选择使用合适的枚举方法。

2.http://www.oschina.net/translate/nsarray-enumeration-performance

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值