在 《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. 2016021801 - Java内存区域归纳对比

    线程私有 线程共享 程序计数器,虚拟机栈,本地方法栈 堆,方法区 内存区 异常 异常原因 程序计数器 无 虚拟机栈 StackOverflowError 线程请求的栈深度大于虚拟机栈所允许的深度 Ou ...

  2. Python中lstrip使用心得

    lstrip方法用来去除字符串从首位开始与之相匹配的字符.例如: a = 'c' b = 'calendar' print(b.lstrip(a)) 输出结果是 'alendar'. 之前我也一直是这 ...

  3. matlab函数之bsxfun

    bsxfun(fun,A,B) 偶然间发现了这个函数,强大得不得了呀,它的作用是:对两个矩阵A和B之间的每一个元素进行指定的计算(函数fun指定):并且具有自动扩维的作用 例如,A是一个4*3的矩阵, ...

  4. 如何使用 Python 创建一个 NBA 得分图?

    本文意在创建一个得分图,该图同时描绘了从场上不同位置投篮得分的百分比和投篮次数,这和 Austin Clemen 个人网站上的帖子 http://www.austinclemens.com/shotc ...

  5. /etc/shadow字段详解

    1)/etc/shadow 概说: /etc/shadow文件是/etc/passwd 的影子文件,这个文件并不由/etc/passwd 而产生的,这两个文件是应该是对应互补的:shadow内容包括用 ...

  6. 【POJ1275】Cashier Employment

    题目: Description A supermarket in Tehran is open 24 hours a day every day and needs a number of cashi ...

  7. 在linux中使用php将word文档转为pdf

    使用本教程需要在linux中安装openoffice,改页面中有详细的安装与使用教程(http://www.cnblogs.com/sustudy/p/3999628.html). 既然,你看了该教程 ...

  8. JSch - Java实现的SFTP(文件下载详解篇)(转)

    上一篇讲述了使用JSch实现文件上传的功能,这一篇主要讲述一下JSch实现文件下载的功能.并介绍一些SFTP的辅助方法,如cd,ls等.   同样,JSch的文件下载也支持三种传输模式:OVERWRI ...

  9. QML学习:Rectangle,Text,TextEdit,Flickable,Flipable元素

    QML学习:Rectangle,Text,TextEdit,Flickable,Flipable元素 本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明. 参 ...

  10. gcc c语言中scanf输入格式不正确,清空缓冲区问题

    我的博客:www.while0.com 折磨了一下午,只因为fflush(stdin)再gcc里和vc里表现不一致.gcc里不能够清空缓冲区.直接上例子: #include <stdio.h&g ...