Block总结

一、简介
Block代码块,本质上它和其它变量类似,不同的是代码块传递的是函数体,类似于@selector的功能。调用和其它标准函数一样。声明方式有差别。

二、代码块定义
例:int  ( ^    MyBlock)( int ) = ^  (int m){ return m * 3; };
        1    2         3         4       5        6          7
含义:
1:返回类型
2:^是代码块的语法标记
3:代码块的变量名
4:参数类型
5:定义代码块对象
6:参数名是m
7:代码块对象的主体部分

三、代码块调用
拿上面的为例:int newValue = MyBlock(3);

四、代码块语法的一些规则:
(1)当在block中直接使用局部变量时,局部变量会被当做是常量编码到block中(两个变量),所以不能在Block中直接修改局部变量
(2)代码块如果想要递归调用,代码块变量必须为全局变量或者静态变量。
(3)在代码块中可以使用和改变全局变量和静态变量。
(4)代码块可以使用局部变量,但是要改变值的话,要在局部变量前面加关键字__block。

五、Block存储域
根据Block中是否引用了外部变量,可以将Block存储区域分为三种:NSGlobalBlock、NSStackBlock、NSMallocBlock。
1.NSGlobalBlock-存储在全局数据区域:
对于没有引用外部变量的Block,无论在ARC还是非ARC下,类型都是__NSGlobalBlock__,这种类型的block可以理解成一种全局的block,和全局变量一样。同时,对他进行Copy或者Retain操作也是无效的。
2.NSStackBlock-存储在栈上:而对于引用了外部变量的block,如果没有对他进行copy,他的作用域只会在声明他的函数栈内(类型是__NSStackBlock__)。对其执行retain操作没有作用。
3.NSMallocBlock-存储在堆上:对NSStackBlock类型的block,执行copy操作,block会被复制到堆上。retain和copy对会使其引用计数加1。

说明:一般来说出问题的Block大部分都是NSStackBlock,超过了NSStackBlock的作用域 NSStackBlock 就会销毁。

六、什么时候要对NSConcreteStackBlock执行copy操作?
1.场景:配置在栈上的Block,如果其所属的变量作用域结束该Block就会废弃。这个时候如果继续使用该Block,就应该使用copy方法,将NSConcreteStackBlock拷贝为_NSConcreteMallocBlock。当_NSConcreteMallocBlock的引用计数变为0,该_NSConcreteMallocBlock就会被释放。
2.如果是非ARC环境,需要显式的执行copy或者antorelease方法。
3.而当ARC有效的时候,实际上大部分情况下编译器已经为我们做好了,自动的将Block从栈上复制到堆上。包括以下几个情况:
(1)Block作为返回值时
类似在非ARC的时候,对返回值Block执行[[returnedBlock copy] autorelease];
(2)方法的参数中传递Block时
(3)Cocoa框架中方法名中还有useringBlock等时
(4)GCD相关的一系列API传递Block时。
比如:[mutableAarry addObject:stackBlock];这段代码在非ARC环境下肯定有问题,而在ARC环境下方法参数中传递NSConcreteStackBlock会自动执行copy。

七、一般的应用场景:
假设A要调用B完成一件事,但是在B完成事情之后要通知A一下,这时候可以使用Block。
1.首先在B中定义一个Block类型,比如:
  typedef void (^DoSomeThingFinished)(id parame);
2.定义Block实例变量,DoSomeThingFinished aDoSomeThingFinished;
3.定义B的动作方法:-(void)doSomeThing:(DoSomeThingFinished)doSomeThingFinished;
4.动作方法实现规则:
(1)当doSomeThing方法被调用时,首先将doSomeThingFinished要copy一下(block将从栈赋值到堆上),并且赋值给aDoSomeThingFinished,以防止调用block时,block已经销毁。
(2)当事情完成时,首先检查代码块变量aDoSomeThingFinished是否为nil,如果不为nil,调用aDoSomeThingFinished代码块变量,并传入合适的值。然后release代码块变量aDoSomeThingFinished,并赋值nil。
5.在B销毁时,检查aDoSomeThingFinished是否为nil,如果不为nil,release,并且赋值nil。
6.A调用B的方法,如下:
[b doSomeThing:^(id parame){ /*动作完成时要做的事情*/ }];

八、Block循环引用:
(1)场景:假如A调用B,B的API使用了Block。在A中有一个B的实例作为成员变量,此时A引用了B;A在使用B的API的时候,在Block代码中使用了self关键字或者A的成员变量,导致block引用了A,即B引用了A。从而导致循环引用。
(2)导致的问题:如果Block被很好的执行,并且B release了Block,A的引用计数自然就降下来了,循环引用消失。但是,如果B长时间或者根本没有调用Block,导致B一直引用Block并且没有释放它,从而A的引用计数一直降不下来,导致A不能释放。
(3)解决办法:
MRC:重新定义一下self,如下:__block typeof(self) bself = self; 将self定义为__block类型,在block中使用bself变量,此时block就不会retain当前控制器了。当A销毁前,首先将B销毁掉,B销毁时,代码块被release并置nil,block将不会被执行。__block关键字告诉编译器,不要retain该变量。
ARC:对于ARC下, 为了防止循环引用, 我们使用__weak来修饰在Block中使用的对象。
(4)说明: 在使用Block时,可以大胆的说,百分之九十九的情况下是不需要使用weakSelf的。
Block代码块引用self以至retain当前控制器,是有道理的,这样做是为了让代码块得到很好的执行,如果当前控制器已经释放了,在回调代码块的时候,就不正常了。
我们需要做的是,维护好block的调用关系以及生命周期就可以了,让block及时释放,对当前控制器的引用自然而然也就释放了。
那剩下的那百分之一是什么情况呢?即对Block的持有者一直保持Block不释放的情况,比如以Block的方式使用加速计,除非将加速计停止,否则Block一直会被持有,如果不希望这样,可以在Block中使用weakSelf。

九、Block内存演示代码:

//======================================================

//ARC:正确

//MRC:正确


void exampleA() {

    char a = 'A';

    ^{

        printf("%c\n", a);

    }();

}


//======================================================

//ARC:正确

//MRC:正确---EXC_BAD_ACCESS

//说明:添加的block在栈上,ARC下,block会被copyMRC下,如果没有执行copy操作,此block在函数体结束之后就释放了。

//addObject方法执行的是retain操作,不起作用。


void exampleB() {

    NSMutableArray *array = [NSMutableArray array];

    exampleB_addBlockToArray(array);

    void (^block)() = [array objectAtIndex:0];

    block();

}


void exampleB_addBlockToArray(NSMutableArray *array) {

    char b = 'B';

    [array addObject:^{

        printf("%c\n", b);

    }];

}


//======================================================

//ARC:正确

//MRC:正确

//说明:添加的Block为全局Block,函数体结束之后,它还存在。


void exampleC() {

    NSMutableArray *array = [NSMutableArray array];

    exampleC_addBlockToArray(array);

    void (^block)() = [array objectAtIndex:0];

    block();

}


void exampleC_addBlockToArray(NSMutableArray *array) {

    [array addObject:^{

        printf("C\n");

    }];

}


//======================================================

//ARC:正确

//MRC:正确,编译不通过--Returning block that lives on the local stack

//说明:添加的Block在栈上,ARC下,block会被copyMRC下,如果没有执行copy操作,此block在函数体结束之后就释放了。


typedef void (^dBlock)();


dBlock exampleD_getBlock() {

    char d = 'D';

    return ^{

        printf("%c\n", d);

    };

}


void exampleD() {

    exampleD_getBlock()();

}


//=======================================================

//ARC:正确

//MRC:不正确。这个例子和例子4类似,除了编译器没有认出有错误,所以代码会进行编译然后崩溃。更糟糕的是,这个例子比较特别,如果你关闭了优化,则可以正常运行。所以在测试的时候需要注意。


typedef void (^eBlock)();


eBlock exampleE_getBlock() {

    char e = 'E';

    void (^block)() = ^{

        printf("%c\n", e);

    };

    return block;

}


void exampleE() {

    eBlock block = exampleE_getBlock();

    block();

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值