NSArray 在使用 enumerateObjectsUsingBlock 时 block 中的 stop 为什么是 *stop

刚遇到这个问题,还是有点摸不着头脑,说不出这样设计是出于怎么考虑,当然,设计初衷无非就是用指针对象更容易容易实现xx 功能,避免 xx bug等等,但只这样说没有实践过是无法作为呈堂供词的。
遇到这样的问题可以反过来考虑,如果让自己来实现类似 NSArrayenumerateObjectsUsingBlock 的实例方法,那么该怎么设计呢?

为了不影响 NSArray 原有的方法,自己加个 category
先写一个简单点不考虑 stop 的方法:

- (void)xx_enumerateObjectsUsingBlockWithoutStop:(void (^)(id obj, NSUInteger idx))block;

实现方法简单点(先省去各种判断)

- (void)xx_enumerateObjectsUsingBlockWithoutStop:(void (^)(id obj, NSUInteger idx))block {
    
    int i = 0;
    
    for (id tempObject in self) {
        
        if (block) {
            block(tempObject, i);
        }
        i++;
    }
}

随便写个类验证一下:

 NSArray *array = @[@"ABC",
                       @"DEF",
                       @"GHI",
                       @"JKL",
                       @"MNO",
                       @"PQR"];
    
    
    [array xx_enumerateObjectsUsingBlockWithoutStop:^(id  _Nonnull obj, NSUInteger idx) {
        
        NSLog(@"%ld -- %@", (long)idx, obj);
    }];
2019-07-28 22:53:43.850316+0800 HaveFun[910:41332] 0 -- ABC
2019-07-28 22:53:43.850451+0800 HaveFun[910:41332] 1 -- DEF
2019-07-28 22:53:43.850515+0800 HaveFun[910:41332] 2 -- GHI
2019-07-28 22:53:43.850577+0800 HaveFun[910:41332] 3 -- JKL
2019-07-28 22:53:43.850642+0800 HaveFun[910:41332] 4 -- MNO
2019-07-28 22:53:43.850697+0800 HaveFun[910:41332] 5 -- PQR

现在考虑 stop 的情况,stop 的作用就是终止 for 循环,表现出来的应该类似于

for (id tempObject in self) {
        
        /*
         循环体
         */
        
        if (stop) {
            break;
        }
    }

如果在不了解 enumerateObjectsUsingBlock 方法之前实现类似功能,可能会写出这样的代码,(当然,规范来说,一般会将block 写在参数列表的最后面)

- (void)xx_enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx))block stopIndex:(NSInteger)index {
    
    int i = 0;
    
    for (id tempObject in self) {
        
        if (block) {
            block(tempObject, i);
        }
        
        if (index == i) {
            break;
        }
        
        i++;
    }
}

这样写虽然实现了指定条件下的终止循环,但并没有什么实际意义,试想一下,如果已经知道需要的遍历次数,那么直接缩小遍历范围就可以,没必要多此一举。需求的目的是要在遍历过程中如果找到了目标值,那么接下来的遍历就不需要了。例如:查找一次考试中姓名是 “小满” 的同学的成绩,(这里假定姓名唯一),遍历之前并不晓得名字是 “小满”是在列表中的顺序,可能是第一位,也可能是最后一位,但只要满足

if (array[i].name == @"小满") {
    NSLog(@"成绩: %lf", array[i].score);
    break;
}

这样列表后面的同学成绩就不需要查找了,可以有效的降低遍历次数,达到节能减排的目的。

走一波原方法

    NSArray *array = @[@"ABC",
                       @"DEF",
                       @"GHI",
                       @"JKL",
                       @"MNO",
                       @"PQR"];
    
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (3 == idx) {
            *stop = YES;
        }
        NSLog(@"%ld -- %@, %@", (long)idx, obj, *stop == YES ? @"YES":@"NO");
    }];
2019-07-28 23:37:25.791822+0800 HaveFun[1286:80311] 0 -- ABC, NO
2019-07-28 23:37:25.792015+0800 HaveFun[1286:80311] 1 -- DEF, NO
2019-07-28 23:37:25.792062+0800 HaveFun[1286:80311] 2 -- GHI, NO
2019-07-28 23:37:25.792123+0800 HaveFun[1286:80311] 3 -- JKL, YES

*stop = YES; 时,会执行完这次 block 中的循环,下次循环就不再执行了。

这里需要的是在每次循环的过程中,重新获取下是否需要终止循环

尝试着根据原 api 来(简单)实现一波

- (void)xx_enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop)) block {
    
    // 终止标志
    BOOL stopFlag = NO;

    for (NSUInteger i=0; i

调用方式是一样的

    [array xx_enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        if (2 == idx) {
            *stop = YES;
        }
        NSLog(@"%ld -- %@ ", (long)idx, obj);
    }];
2019-07-29 09:40:38.963120+0800 HaveFun[984:30399] 0 -- ABC
2019-07-29 09:40:38.963290+0800 HaveFun[984:30399] StopFlag === 0
2019-07-29 09:40:38.963381+0800 HaveFun[984:30399] 1 -- DEF 
2019-07-29 09:40:38.963474+0800 HaveFun[984:30399] StopFlag === 0
2019-07-29 09:40:38.963592+0800 HaveFun[984:30399] 2 -- GHI 
2019-07-29 09:40:38.963676+0800 HaveFun[984:30399] StopFlag === 1

如果把 Bool *stop 替换为你 Bool stop

- (void)xx_enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL stop)) block {
    
    // 终止标志
    BOOL stopFlag = NO;

    for (NSUInteger i=0; i

调用

[array xx_enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL stop) {
        
        if (2 == idx) {
            stop = YES;
        }
        NSLog(@"%ld -- %@ ", (long)idx, obj);
    }];

很遗憾

2019-07-29 10:06:02.963879+0800 HaveFun[1211:53383] 0 -- ABC
2019-07-29 10:06:02.964073+0800 HaveFun[1211:53383] StopFlag === 0
2019-07-29 10:06:02.964157+0800 HaveFun[1211:53383] 1 -- DEF
2019-07-29 10:06:02.964235+0800 HaveFun[1211:53383] StopFlag === 0
2019-07-29 10:06:02.964310+0800 HaveFun[1211:53383] 2 -- GHI
2019-07-29 10:06:02.964384+0800 HaveFun[1211:53383] StopFlag === 0
2019-07-29 10:06:02.964456+0800 HaveFun[1211:53383] 3 -- JKL
2019-07-29 10:06:02.964528+0800 HaveFun[1211:53383] StopFlag === 0
2019-07-29 10:06:02.964600+0800 HaveFun[1211:53383] 4 -- MNO
2019-07-29 10:06:02.964764+0800 HaveFun[1211:53383] StopFlag === 0
2019-07-29 10:06:02.964986+0800 HaveFun[1211:53383] 5 -- PQR
2019-07-29 10:06:02.965202+0800 HaveFun[1211:53383] StopFlag === 0

说明 Bool stop 并不能控制循环的终止条件。
我们知道指针操作都是对物理内存的操作,把 stopFlag 的内存地址 &stopFlag 传给调用者, 那么调用者就可以与实现函数共享 stopFlag 变量。根据猜想,代码稍作修改,验证下:
在实现和调用函数中都加入当前 stop 地址的log

NSLog(@"StopFlag === %d, Addr = %p", stopFlag, &stopFlag); // 实现函数中加入
NSLog(@"%ld -- %@, SubAddr: %p", (long)idx, obj, &stop); //调用函数中加入
2019-07-29 10:14:58.928249+0800 HaveFun[1293:60577] 0 -- ABC, SubAddr: 0x7ffeebe91137
2019-07-29 10:14:58.928392+0800 HaveFun[1293:60577] StopFlag === 0, Addr = 0x7ffeebe911a7
...
...

可见根本不是同一个变量,所以无论调用者怎么改变 stop 的值都是无济于事的。

由此可见,若想实现调用者与函数实现共享内存的方法,需要使用一级指针。

你可能感兴趣的:(NSArray 在使用 enumerateObjectsUsingBlock 时 block 中的 stop 为什么是 *stop)