一.Block简单的使用
1.block当作参数来传递
如下定义一个没有返回值无参数的block,并把它作为参数,让系统调用,注意:这里是系统在调用,不是我们调用
那么为什么需要把block当作参数去使用呢?
这就引出了block这个时候的使用场景:当自己封装一个类的时候,有些事情由外部决定,但什么时候做由内部决定,(即内部决定执行时间,外部传入具体做些什么)——这个时候就可以使用block来作为参数
2.block当作返回值来使用
如下代码,test为方法名,void(^)()这就是block的类型,这样就可以这样使用,其中的的self.test是相当于属性的get方法,后面再加一个()就相当于执行block.
同时时还可以将这个block给相同类型的bock赋值.如下代码所示.
所以myBlock后加一个小括号就是执行,这样就能理解为什么上面self.test加一个小括号就是执行.——这让我联想到swift里执行函数都是点点点,并且加上()就是创建对象执行函数等等,原来都是这么的类似....
block当返回值使用思想:链式编程,把方法调用通过语法链接起来,可读性非常好
下面简单来演示一下用Block来进行链式编程
创建一个类继承NSObject命名为AddTool,即到时我们要用它来进行链式累加,它的.h文件里如下
它的.m文件如下:
然后在ViewController类里进行调用如下
运行起来,打印结果为,OK完成!
二.Block逆传值的使用
Block可以用于控制器界面间逆向传值.使用方法是这样:控制器B传值给控制器A, 那么要在控制器B中的头文件.h声明一个Block属性, 并且在控制器B的.m文件中执行这个Block.
代码表示如下:
-
- 在B的头文件中
@property (nonatomic,strong) void(^myBlock)(NSString *);
说明:这里定义了一个无返回值,参数为NSString *的Block,Block的名字为myBlock,这就类比声明一个函数void test(int);——无返回值,参数为int,函数名为test.
如果是创建一个int返回值的block将上面的void改为int
且这里的Block类型为strong类型,关于Block的修辞策略后面再讲.
-
- 在B的.m文件中这样使用
if (_myBlock) {
_myBlock(@"1234567");
NSLog(@"%@", str);
}
说明:这里的if是对Block先进行判断,作用是判断Block是否已经分配好内存,如果定义完成就执行,并传入了一个字符串参数
如果block有返回值,那么可以用一个值来接收它的返回值如int num = _myBlock(@“1234567”);
-
- 在A的.m文件中
先importB的头文件,再创建一个B的具体对象如下为myVC,然后
myViewController *myVC = segue.destinationViewController;
myVC.myBlock = ^(NSString *str){
NSLog(@"%@", str);
};
说明:如果block有返回值,那需要在Block内部加上一句return才行.如return 10;这样myBlock就有了值,从而B中的num也就有了值.即这个有值的过程是先内部的block有值然后执行完外部才有值.
三.Block的循环引用问题
1.简单循环引用
循环引用的描述:就是当对象不想使用时本应释放,但由于循环强引用,谁也释放不了谁造成内存泄露.
Block发生循环引用的场合:ARC中Block为strong或copy属性,在Block内部使用了当前类的self属性,同时这个类包含了别一个类的Block属性.
举例:还是上面的传值的例子,现假如在A的.m文件的block里使用了self.view.backgroundColor = [UIColor redColor];现在这些对象之间的引用关系如下图所示:
因此这样就就造成了循环引用.
Block内循环引用的解决:
在如下的例子,可以在Block外加上这样一句(如果将typeof用于表达式,则该表达式不会执行。只会得到该表达式的类型。)
__weak typeof(self) weakSelf = self;
或者这样也可以
__weak ViewController *weakSelf = self;
2.复杂循环引用
这个还没遇到过,只是看到有这样用过.....这个后面有时间再分析!
四.Block的内存分析(ARC与非ARC下的验证)
如何验证arc还是非arc,一点点回顾
1.arc中无法调用retain和release,并且dealloc方法不能调用super dealloc.非ARC中super dealloc写在最后面
2.项目 -> build settings -> ARC
ARC的set方法如下,与上次值不相等,就释放旧值,retain新值..所以我们在ARC应尽量用self.这种方式去调用set方法,而不要直接使用下划线,因为会内存泄露.
非ARC中没有weak,没有strong,只有对应的assign和retain
strong与copy:因为上面这里字符串用到了copy,而且上面代码也是ARC下copy内部做的事,也就是它会release旧值,将新字符串name重新copy一份赋值给旧_name.这样当我们改变_name时,不会影响到name.(ps:arc下一般如果不会改变的字符串就尽量用strong,因为省得copy一份出来,相比而言copy更耗性能,对于ARC下使用Block也是如此,不需要copy就用strong就好)
内存5大区
内存分为5大区:堆, 栈, 方法区, 静态区(全局区), 常量区
ps: 全局区和静态区其实是一样的,从内存上来看,全局变量和静态变量都是保存在静态存储区,生命期和程序一样,都是在静态数据区分配内存.在程序结束后回收内存.
它们之间作用域有所不同,全局变量的作用域是整个项目,静态全局变量是当前程序文件,静态局部变量是当前函数体内.
堆:手动管理内存 栈:代码块一过,系统自动回收对应内存区
首先来了解一下Block,苹果官方文档Block是对象.(因为它是对象,所以用%@格式打印可以看到它的内存分配区),文档描述第一句话如下:
非ARC下:
1.全局区的情况:如下定义一个Block,并将它使用, 它的打印结果为,global表示全局区.全局区表示到处都可以使用.(ps:假如block内部访问static修辞的外部局变量,那么它也是在全局区,关于静态区与全局刚刚上面已经解释过)
2.栈区:如下当Block内访问一个外面的局部变量a,它的打印结果为,stack表示栈区
总结如下:Block的内存分配与它内部访问的变量有关,如果访问的是全局变量,那Block会在全局区; 如果访问局部变量,那Block会分配到栈区.(什么也不访问默认在全局区)
3.堆区:使用copy进行强引用时block会copy一份到堆区
在非ARC环境下用retain修辞Block会有黄色警告,如下图,警告提示最好使用copy
假如现在就用retain,并且在block内访问了一个局部变量a(这个a就写在Block的上面),代码如下,这时发现一运行程序就漰了.(坏内存访问,报错会出现在使用self.Block的地方)
这一点总结:在非ARC下不能用retain引用block,因为这样不会把block放在堆里,它会在栈区,所以代码区一出括号就会销毁,于是会报错.只要使用copy就可以把block放到堆里面.这样block就不会销毁.——所以说block要用copy都是老程序员说的,因为那时没有ARC.
ARC下:
1.全局区:还是如下代码,默认Block还是放在全局区,没访问外部变量就都是在全局区,与是否ARC无关
2.堆:访问一个外部局部变量或对象时,代码如下,它的打印结果,这个malloc分配的内存是表示在堆上.(这有点像ARC下默认一个对象就是强引用,好处是不会一创建就销毁.)
五.Block的内变量的传递与改变
看下面代码,这个很简单,block打印出来肯定是10
值传递
再看如下代码,这个打印出来是5, 虽然block()执行之前a = 10,但,因为a = 5之后就立即被传递进了block,只是还没有执行而已,即内存已配好.而且这时的block是值传递,这时外面更改无法改变里面的值.(如果是指针传递则可以)
指针传递
那么再看如下代码,这个打印结果会是10,原因:只要局部变量生命周期是整个app运行都在,那么就是指针传递
这里加static后,生命周期就与整个程序同存亡了.与全局区一样(这一点上面Block内存分析时分析过了)
下在情况也是指针传递,所以打印出来也会是10
block变量传递总结:如果在block内部访问的是局部变量,那么就是值传递,否则就是指针传递