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<self.count; i++) {
        
        if (block != nil) {
            // 把‘终止标志’的地址传给调用者
            block(self[i], i, &stopFlag);
        }
        
        NSLog(@"StopFlag === %d", stopFlag);
        
        // ‘终止标志’改变,则终止循环
        if (stopFlag == YES) {
            break;
        }
    }
}

调用方式是一样的

    [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<self.count; i++) {
        
        if (block != nil) {
            // 把‘终止标志’的地址传给调用者
            block(self[i], i, stopFlag);
        }
        
        NSLog(@"StopFlag === %d", stopFlag);
        
        // 终止标志改变,则终止循环
        if (stopFlag == YES) {
            break;
        }
    }
}

调用

[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 的值都是无济于事的。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,074评论 1 32
  • 前言 说是前言,其实也是本文诞生的目的。随着公司业务的不断增加,功能的快速迭代,app的业务线越来越多,代码体积变...
    Yealink阅读 5,260评论 0 13
  • 记录-- 下面的例子以 NSArray *array = [NSArray arrayWithObjects:@"...
    北极圈生物阅读 5,625评论 0 1
  • 前言 说是前言,其实也是本文诞生的目的。随着公司业务的不断增加,功能的快速迭代,app的业务线越来越多,代码体积变...
    梦翔_d674阅读 1,484评论 0 2
  • 猛虎在人间, 蔷薇绣宿缘。 昙花当问世, 不敢与君言。 --- 音乐:Gymnopedie
    海子三月归阅读 408评论 4 21