iOS开发之block详解
2016-02-26 23:11
423 查看
前言
今天研究了一下iOS开发中的block,有些心得,故写下来。默认读者会是有 iOS开发经验的,故相关内容不再科普。1.block入门
老子说:学习编程,从翻译开始。block的翻译是什么?是代码块。代码块是个什么鬼?block就是一块可以使用的代码,在定义和声明上和变量类似,在使用上和函数类似。
下面上demo,首先是全局的代码块:
void (^foo)(void) = ^(void){ NSLog(@"Hello World!"); };
以及全局变量:
static int _index = 0;
和函数
void func(void) { NSLog(@"Hello World!"); }
由上面可以看到,block即像函数一样有参数列表,又像基本数据类型一样是有定义声明,最后需要接分号“;”的。
而block在使用中,更加像函数:
foo();
而拆开的block的定义和声明,又和变量的定义和声明比较像:
void (^block)(void); block = ^(void){ NSLog(@"Hello World!"); };
对block有了基本的认识,下面来看下block有哪些特性,以及需要注意的问题。
2.block访问局部变量
block对上下文局部变量的访问,又有一个高大上的名词,就是“闭包性”。事实上,block的上下文访问,并不是直接访问,而是做了一个备份,对备份的访问。2.1.block访问局部变量——读取
block对内部局部变量的控制,类似于一个维护着的参数表。block内部使用到的所有变量,都会另外申请新的内存空间。对于block的参数,自然是和函数的参数类似,遵循一般的取值取址操作。对于block访问的外部的局部变量,是获取的一份局部变量的数据拷贝。所以,局部变量的声明周期结束,所占的内存释放后,block仍旧可以获取其中的数据:
void (^block)(void); - (void)blockTest01 { int i = 10; block = ^(void) { NSLog(@"%d",i); }; } - (void)blockTest11 { [self blockTest1]; block(); }
通过打印地址,我们可以看到,block对局部变量的访问确实是值访问。
- (void)blockTest02 { int i = 10; NSLog(@"局部变量i --- %p", &i); block = ^(void) { NSLog(@"block访问i --- %p",&i); }; }
运行结果为:
局部变量i --- 0x7fff58ef2a7c block访问i --- 0x7fd8fa507280
以上,基本可以确定,block访问局部变量是单独拷贝了一份数据。说[访问][6]其实不是很准确,因为局部变量的声明周期早已结束,这里实际上是block私下备了个份。
而block备份的方式是值拷贝,所以block对局部变量的访问会出现和函数参数类似的值传递、址传递的问题。具体下面说。
2.2.block访问局部变量——址传递的修改
有C语言功底的朋友应该知道,指针中存放的值,是实际值在内存中的地址。而block中对局部变量的访问,就是一种值访问。当该局部变量是一般的数据时:
int i = 0;
拷贝的是i的值,即:“0”。因此,block对于该局部变量的修改,仅限于其备份,而对其本身是起不到任何作用的。
然而,当局部变量是指针时:
char *str = "Hello";
str中存储的是“Hello”这个字符串在内存中所在的位置。即便局部变量的声明周期结束后,由于block中的str的备份仍然持有该内存,故该内存是不会被释放,此处遵守C语言内存管理原则[谁拥有谁释放][6]。由于block持有了str的地址,因此对其备份的修改,就是对内存地址所指向内容的修改。
当然,上例中,str是在常量区的,是无法直接修改的,这又是另一个话题了。
至此,block对一般的局部变量的访问就讲完了。
2.3.block访问局部变量——值传递的修改
接上节,当遇到block值传递数据,比如:int i = 0;
我们怎么实现对其的修改呢。根据我们对C语言值传递的经验,我们很容易知道,只要重新获得i的地址,进行传递就可以了,比如:
- (void)blockTest03 { static int i = 10; int *pi = &i; block = ^(void) { (*pi)++; NSLog(@"值:%d,址:%p",*pi, pi); }; NSLog(@"局部变量i:%d",i); }
当我们多次运行block,再重新打印局部变量时:
[self blockTest03]; block(); block(); block(); [self blockTest03];
会发现
局部变量i:10 值:11,址:0x10205fe18 值:12,址:0x10205fe18 值:13,址:0x10205fe18 局部变量i:13
也就是说,我们成功的通过址传递,完成了在block内部对局部变量的修改。
当然,这种方法很C风格,对OC程序员很不友好,因此苹果给了我们一个代码糖__block,来实现相同的功能。
__block typeof(i) bi = i;
这里的bi在使用中和*pi相同。
总结一下:
__block是用来将局部变量的访问改为取址操作的。
简单的讲,就是解决无法修改局部变量值的一个方法。
2.4.block访问局部变量——循环引用
当出现以下场景时:某个类FooClass有一个成员变量barBlock,以及一个成员变量name。当barBlock内部通过self使用了name时,就会出现以下情况:barBlock持有self的地址,self持有barBlock的地址,二者内存地址相互持有,导致二者均无法正确释放,造成内存泄露。
用OC的说法就是,二者相互强引用,导致内存无法释放。解决办法就是,将其中一个设置为若引用。
这个就是我们常说的block循环引用,以及其解决办法:
__weak typeof(self) wSelf = self;
总结一下:
__weak是用来解决在block调用“拥有该block的对象的成员变量”时,造成的循环引用问题的。
简单的将,就是当block里面用到self时,要用上面的代码。
2.5.__block和__weak的区别
如果读过上面的部分,应该知道,二者的区别是很大的。只是由于二者用的时候看起来很像,故而有可能会被混淆。总结一下:
__block是对于无法修改的局部变量,使得block能够修改的方法
__weak是对于可修改的变量self,解决其使用中会造成循环引用问题的一个办法。
3.block的本质
其实从底层实现上,block和NSObject一样,是用struct实现的。这也就解释了,为什么block的定义和声明会和变量的声明非常相似。block的实现以及其闭包性,就是通过该struct实现的。该struct内有两个比较重要的成员:指向实际执行的函数指针、block内部调用的成员变量表。就是在该变量表中,存放着上下文临时变量的拷贝。
具体的细节,有一篇转载的博文Block的引用循环问题 (ARC & non-ARC),以及Objective-C中的Block),讲的比较详细,有兴趣的同学可以去研究一下。
相关文章推荐
- ios自动释放池
- ios-提升之【5】-viewController的loadView以及view的属性
- 基于CocoaPods的iOS项目模块化实践
- ios-基础之【1】-类定义
- ios 在已有项目添加CoreData
- iOS 设计模式 - 观察者模式
- iOS 设计模式 - 组合模式
- iOS 设计模式 - 生成器模式
- 使用IOS7原生API进行二维码条形码的扫描
- RTMP协议实现IOS播放HEAAC思路
- iOS文件解析之XML解析
- IOS 文件夹创建,文件读写删除
- ios 相册获取图片模糊解决办法
- ios 通知和代理的区别
- iOS开发系列--数据存取
- iOS开发系列--地图与定位
- iOS开发系列--通知与消息机制
- iOS开发系列--触摸事件、手势识别、摇晃事件、耳机线控
- iOS开发系列--视图切换
- iOS开发系列--无限循环的图片浏览器