在 《iOS面试题集锦(附答案)》 中有这样一道题目:

在block内如何修改block外部变量?(38题)答案如下:

默认情况下,在block中访问的外部变量是复制过去的,即:写操作不对原变量生效。但是你可以加上 __block 来让其写操作生效,示例代码如下:

__block int a = 0;

void (^foo)(void) = ^{

a = 1;

};

foo();

//这里,a的值被修改为1

这是 微博@唐巧_boy的《iOS开发进阶》中的第11.2.3章节中的描述。你同样可以在面试中这样回答,但你并没有答到“点子上”。真正的原因,并没有书这本书里写的这么“神奇”,而且这种说法也有点牵强。面试官肯定会追问“为什么写操作就生效了?”真正的原因是这样的:

我们都知道:Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。

Block不允许修改外部变量的值Apple这样设计,应该是考虑到了block的特殊性,block也属于“函数”的范畴,变量进入block,实际就是已经改变了作用域。在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。又比如我想在block内声明了一个与外部同名的变量,此时是允许呢还是不允许呢?只有加上了这样的限制,这样的情景才能实现。

我们可以打印下内存地址来进行验证:

__block int a = 0;

NSLog(@"定义前:%p", &a);         //栈区

void (^foo)(void) = ^{

a = 1;

NSLog(@"block内部:%p", &a);    //堆区

};

NSLog(@"定义后:%p", &a);         //堆区

foo();

2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] 定义前:0x16fda86f8

2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] 定义后:0x155b22fc8

2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] block内部: 0x155b22fc8

“定义后”和“block内部”两者的内存地址是一样的,我们都知道 block 内部的变量会被 copy 到堆区,“block内部”打印的是堆地址,因而也就可以知道,“定义后”打印的也是堆的地址。

那么如何证明“block内部”打印的是堆地址?

把三个16进制的内存地址转成10进制就是:

  1. 定义后前:6171559672

  2. block内部:5732708296

  3. 定义后后:5732708296

中间相差438851376个字节,也就是 418.5M 的空间,因为堆地址要小于栈地址,又因为iOS中一个进程的栈区内存只有1M,Mac也只有8M,显然a已经是在堆区了。

这也证实了:a 在定义前是栈区,但只要进入了 block 区域,就变成了堆区。这才是 __block 关键字的真正作用。

理解到这是因为堆栈地址的变更,而非所谓的“写操作生效”,这一点至关重要,要不然你如何解释下面这个现象:

以下代码编译可以通过,并且在block中成功将a的从Tom修改为Jerry。

NSMutableString *a = [NSMutableString stringWithString:@"Tom"];

NSLog(@"\n 定以前:------------------------------------\n\

a指向的堆中地址:%p;a在栈中的指针地址:%p", a, &a);               //a在栈区

void (^foo)(void) = ^{

a.string = @"Jerry";

NSLog(@"\n block内部:------------------------------------\n\

a指向的堆中地址:%p;a在栈中的指针地址:%p", a, &a);               //a在栈区

a = [NSMutableString stringWithString:@"William"];

};

foo();

NSLog(@"\n 定以后:------------------------------------\n\

a指向的堆中地址:%p;a在栈中的指针地址:%p", a, &a);

这里的a已经由基本数据类型,变成了对象类型。对象类型,block会对对象类型的指针进行copy,copy到堆中,但并不会改变该指针所指向的堆中的地址,所以在上面的示例代码中,block体内修改的实际是a指向的堆中的内容。

但如果我们尝试像上面图片中的65行那样做,结果会编译不通过,那是因为此时你在修改的就不是堆中的内容,而是栈中的内容。

上文已经说过:Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。

iOS中__block 关键字的底层实现原理的更多相关文章

  1. iOS __block 关键字的底层实现原理 -- 堆栈地址的变更

    默认情况下,在block中访问的外部变量是复制过去的.但是可以加上 __block  来让其写操作生效. 原理: Block 不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址. ...

  2. Java的 volatile关键字的底层实现原理

    我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用.本文详细解读一下volat ...

  3. ios中创建可以拖动的view原理和实现详解

    有时候我们会需要在界面上拖动view;uiview是继承于uiresponder的,所以可以响应触摸相关的事件. 重点是以下一组方法: - (void)touchesBegan:(NSSet *)to ...

  4. (转)在iOS中使用icon font

    http://ued.taobao.org/blog/?p=8579 在开发阿里数据iOS版客户端的时候,由于项目进度很紧,项目里的所有图标都是用最平常的背景图片方案来实现.而为了要兼容普通屏与Ret ...

  5. 在iOS中使用icon font

    博文转载至 http://www.cocoachina.com/industry/20131111/7327.html 在开发阿里数据iOS版客户端的时候,由于项目进度很紧,项目里的所有图标都是用最平 ...

  6. 在 iOS 中使用 iconfont

    如何使用自定义字体 在讲icon font之前,首先先来看看普通自定义字体是如何在ios中使用的,两个原理是一样的.这里以KaushanScript-Regular为例:   Step 1: 导入字体 ...

  7. iOS分类底层实现原理小记

    摘要:iOS分类底层是怎么实现的?本文将分如下四个模块进行探究分类的结构体编译时的分类分类的加载总结本文使用的runtime源码版本是objc4-680文中类与分类代码如下//类@interfaceP ...

  8. 前端知识体系:JavaScript基础-原型和原型链-理解 es6 中class构造以及继承的底层实现原理

    理解 es6 中class构造以及继承的底层实现原理 原文链接:https://blog.csdn.net/qq_34149805/article/details/86105123 1.ES6 cla ...

  9. Java volatile 关键字底层实现原理解析

    本文转载自Java volatile 关键字底层实现原理解析 导语 在Java多线程并发编程中,volatile关键词扮演着重要角色,它是轻量级的synchronized,在多处理器开发中保证了共享变 ...

随机推荐

  1. shell编程的一些例子1

    1.$0-$9及$# $@的使用 demo_arg 内容 #!/bin/bash echo "程序名:$0" echo "命令传递参数个数:$#" echo & ...

  2. ubuntu修改主机名称

    一.为何要修改主机名称 在安装Ubuntu时,一不小心主机名称输入的“too long”了,导致命令行上就提示符占据了一行的1/3,迫使我产生修改主机名称的冲动. 二.操作方法 原理:系统启动时,会从 ...

  3. 4542: [Hnoi2016]大数

    Description 小 B 有一个很大的数 S,长度达到了 N 位:这个数可以看成是一个串,它可能有前导 0,例如00009312345.小B还有一个素数P.现在,小 B 提出了 M 个询问,每个 ...

  4. 拦截QT关闭窗口的CloseEvent

    QDialog类下有一个虚函数 void QDialog::closeEvent (  QCloseEvent   *  e   )  [virtual protected] 通过实现closeEve ...

  5. hdu 2955 Robberies 背包DP

    Robberies Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total S ...

  6. javascript for

    var k = ["a", "b", "c", false]; for(var i = 0, m; m = k[i]; i++) { con ...

  7. 在linux下的apache配置https协议,开启ssl连接

    环境:linux 配置https协议,需要2大步骤: 一.生成服务器证书 1.安装openssl软件 yum install -y openssl mod_ssl 2.生成服务器私匙,生成server ...

  8. SlimFTPd, LFTP和FileZilla Client/Server

    https://lftp.yar.ru/ 绝好的Socket项目

  9. 翻译qmake文档 目录(四篇)

    http://www.cnblogs.com/li-peng/p/4026133.html

  10. scanf(),fscanf的详解

    我们这里只讨论fscanf(或者scanf)的格式,因为这些细节在其他贴里并没有涉及,阅读此文,你可以少走一些弯路.只讲结果,深层原因并不分析. FILE *pFile:float x1; char ...