(0032) iOS 开发之Block 的基础用法及注意事项1


该文章参考多篇文章,已记不清,如有问题请联系我。

参考:http://blog.csdn.net/zm_yh/article/details/51469275


Block理解

1. Block执行的代码,是在编译的时候已经生成好的;

2.  Block一个包含执行时需要的所有外部变量值的数据结构。 Block将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上。

Block的修饰

ARC情况下

1.如果用copy修饰Block,该Block就会存储在堆空间。则会对Block的内部对象进行强引用,导致循环引用。内存无法释放。

解决方法:

新建一个指针(__weak typeof(Target) weakTarget = Target )指向Block代码块里的对象,然后用weakTarget进行操作。就可以解决循环引用问题。 ARC中 GCD 中并不需要对self 弱引用,因为self  并不持有 GCD 的block 直接self即可。 


MRC情况下

copy修饰后,如果要在Block内部使用对象,则需要进行(__block typeof(Target) blockTarget = Target )处理。在Block里面用blockTarget进行操作。


Block的定义格式

  返回值类型     Block变量名            形参列表          传递         形参列表

  NSString* (^ TestBlock)(id parameA,id parameB…) = ^(id parame1,id parame2…) { };

       调用Block的代码:block变量名(实参);


安全调用block

一般调用block调用时都要对其是否为nil进行判断,是处于安全的考虑,如下

if(block) {

    block();

}


Block的模式

1.无参数无返回值的Block

/**

 *  无参数无返回值的Block

 */

- (void)func1{

    /**

     *  void :就是无返回值

     *  emptyBlock:就是该block的名字

     *  ():这里相当于放参数。由于这里是无参数,所以就什么都不写

     */

    void (^emptyBlock)() = ^(){

        NSLog(@"无参数,无返回值的Block");

    };

    emptyBlock();

}


2.有参数无返回值的Block

/**

     *  调用这个block进行两个参数相加

     *  @param int 参数A

     *  @param int 参数B

     *

     *  @return 无返回值

     */

    void (^sumBlock)(int ,int ) = ^(int a,int b){

        NSLog(@"%d + %d = %d",a,b,a+b);

    };

    /**

     *  调用这个sumBlockBlock,得到的结果是20

     */

    sumBlock(10,10);


3.有参数有返回值的Block

/**

     *  有参数有返回值

     *  @param NSString 字符串1

     *  @param NSString 字符串2

     *

     *  @return 返回拼接好的字符串3

     */    

    NSString *(^logBlock)(NSString *,NSString *) = ^(NSString * str1,NSString *str2){

        return [NSString stringWithFormat:@"%@%@",str1,str2];

    };

    //调用logBlock,输出的是 我是Block

    NSLog(@"%@", logBlock(@"我是",@"Block"));


4. Block作为参数使用

typedef long (^BlkSum)(int, int);

- (BlkSum) sumBlock {

    int base = 100;

    BlkSum blk = ^ long (int a, int b) {

        return base + a + b;

    };

    return [blk copy];

}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

    BlkSum tttt = [self sumBlock]; 

    tttt(10,20);

    NSLog(@"tttt----%ld",tttt(10,20));

}


Block结合typedef使用,作为属性


.h 

/**

 *  定义了一个logBlockBlock。这个logBlock必须带2参数,这个参数的类型必须为NSString类型的

 *  返回值类型   NSString

 *  @param NSString

 */

#import <UIKit/UIKit.h>


typedef NSString* (^logBlock)(NSString *,NSString *);


@interface ViewController : UIViewController


@property (nonatomic,copy)logBlock testBlock;


@end


.m

- (void)viewDidLoad {

    [super viewDidLoad];

    self.testBlock = ^(NSString * str1,NSString *str2){

        return [NSString stringWithFormat:@"%@%@",str1,str2];

    };

}


// 调用

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

    NSLog(@"gggggg----%@",self.testBlock(@"gggg",@"tttt"));

}


block变量定义时为什么用copyblock是放在哪里的?

  • block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,可能被随时回收,而不是在堆(heap)上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。通过copy可以把block拷贝(copy)到堆,保证block的声明域外使用。

特别注意:在把block放到集合类当中去的时候,如果直接把生成的block放入到集合类中,是无法在其他地方使用block,必须要对block进行copy

[array addObject:[[^{

    NSLog(@"hello!");

} copy] autorelease]];


Block与变量

1. Block与局部变量


  1. 官方文档:写到
  2. Stack (non-static) variables local to the enclosing lexical scope are captured as const variables.
  3. Their values are taken at the point of the block expression within the program. In nested blocks, the value is captured from the nearest enclosing scope.
  1. 意思是:在block 使用中,位于一块封闭函数内存(非静态的)栈变量  被捕捉为const变量。
  2. 它们的值是在程序中的块表达式的点处取的。在嵌套块中,该值从最近的封闭范围捕获。(也就是block代码块上面,最近的值。)

// 局部变量,在Block中是只读的。Block定义时会copy变量的值到栈上,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。

- (void)testParameBlock222

{

    int number = 100;

    void(^TestBlock)(int) = ^(int x){

        // number = number+x;

        // 这句报错,局部变量没用__block 修饰时,在block中只能使用,不能修改它的值。

        NSLog(@"number:%d",number+100);

    };

    number = 200// 这里虽然修改的number的值为200。但是不能影响block 中的值。因为在编译block时已经将number 的值只读copy了一份。

    TestBlock(100);

}


参考:http://www.jianshu.com/p/a0adbaca7f69  这个文章更助于我们理解。

我们可以反编译看一下block里面匿名函数的实现:

  1. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  2.   int a = __cself->a; // bound by copy
  3. printf("%d\n",a);
  4. }
  5. bound by copy这里的注释表示,block对它引用的局部变量做了只读拷贝,也就是说block引用的是局部变量的副本。所以在执行block语法后,即使改写block中使用的局部变量的值也不会影响block执行时局部变量的值。


Block与局部变量__block 

// 在Block中修改局部变量的值,使用__block。

// MARK: __block修饰 修改局部变量的值

- (void)testParameBlock333

{

    __block int a = 10;

    void (^helloBlock)(void) = ^(){

        NSLog(@"%d\n",a);

    };

    a = 11;

    helloBlock();

}


引入__block关键字后,运行结果是11,我们再编译看看block里面匿名函数的实现

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  __Block_byref_a_0 *a = __cself->a; // bound by ref


printf("%d\n",(a->__forwarding->a));

}


struct __Block_byref_a_0 {

  void *__isa;

__Block_byref_a_0 *__forwarding;

 int __flags;

 int __size;

 int a;

};

我们看到局部变量a变成一个构造体对象,而不再是一个整形变量,结构体里包含它原来的整形变量。bound by ref这个注释也表明此时已变成引用传递。


深入研究Block捕获外部变量和__block实现原理http://www.halfrost.com/ios_block/)写的有点深,没看完。


2. Block与全局变量

// MARK: Block与全局变量

// 全局变量存储在,因为全局变量或静态变量在内存中的地址是固定的,Block在读取改变量值的时候是直接从其所在的内存读出的,获取得道是最新值,而不是在定义时copy的常量。

int value = 100;

- (void)testParameBlock444

{

    void (^TestBlock)(int)=^(int x){

        value = value+100;

        NSLog(@"看看是不是喽%d",value);

    };

    TestBlock(100);

    NSLog(@"value----%d",value);

}


3. Block与静态变量

// MARK: Block与静态变量

static int number = 100;

- (void)testParameBlock555

{

    int (^TestBlock)(int) = ^(int x){

        number = number+x;

        NSLog(@"静态变量----%d",number);

        return number;

    };

    NSLog(@"static修饰 使用局部变量的结果:%d",TestBlock(100));

    number = 50;

    NSLog(@"在外面改变number的值,再次调用block的结果:%d",TestBlock(100));

}


Block变量,被__block修饰的变量称作Block变量。基本类型的Block变量等效于全局变量、或静态变量。

1.__block对象在block中是可以被修改、重新赋值的。 

2.__block对象在block中不会被block强引用一次,从而不会出现循环引用问题。


使用了__weak修饰符的对象,作用等同于定义为weak的property。自然不会导致循环引用问题,因为苹果文档已经说的很清楚,当原对象没有任何强引用的时候,弱引用指针也会被设置为nil。

因此,__block和__weak修饰符的区别其实是挺明显的: 

1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。 

2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。 

3.__block对象可以在block中被重新赋值,__weak不可以。 


在block中能否定义新的变量

可以,而且在block内部定义的变量是在【栈区】分配空间的


   //定义一个外部变量

    int sum = 2;

    

    //定义一个有参有返回值的block的别名

    typedef int (^myBlock)(int, int);

    NSLog(@"sum = %p",&sum);//此时sum在栈区

    

    myBlock b1 = ^(int a, int b){

        int sum = 100;

        NSLog(@"内部sum = %p",&sum);//此时的地址在栈区

        return sum + a + b;

    };

    NSLog(@"a + b = %d", b1(1,2));

    NSLog(@"外部sum = %p",&sum);//此时sum在栈区


为什么系统的block,AFN网络请求的block内使用self不会造成循环引用? 而自定义的block如果直接使用了self,或者成员变量的话,都会形成强引用,造成内存泄漏? 

关于这个问题,UIViewAFN还是不一样的。

首先循环引用发生的条件就是持有这个block的对象,被block里边加入的对象持有。当然是强引用。所以UIView的动画block不会造成循环引用的原因就是,这是个类方法,当前控制器不可能强引用一个类,所以循环无法形成。

AFN无循环是因为绝大部分情况下,你的网络类对象是不会被当前控制器引用的,这时就不会形成引用环。当然我不知道AFN是否做了别的处理,按照这样来说的话,如果你的控制器强引用了这个网络类的对象,而且在block里面引用了当前控制器,也是会发生循环引用的。

其实只要抓住循环引用的本质,就不难理解。所谓循环引用,是因为当前控制器在引用着block,而block又引用着self即当前控制器,这样就造成了循环引用。系统的block或者AFNblock的调用并不在当前控制器中调用,那么这个self就不代表当前控制器,那自然也就没有循环引用的问题。以上引用均指强引用


UIView啥的因为是类方法,AFNetworking是因为人家大神自己封装了一个completionBlock,不管你传进来是啥,都给你把循环引用打破。看图



如果块是一个单例持有的,块内又使用了ViewController这个类,会引起循环引用。例子: 

 [[OutsidePacketsSchedule shareInstance] sendParameters:dict requestCmd:@"addCustomEmoReq"responseCmd:@"addCustomEmoRsp"complete:^(idresponse,NSError*error) {  

     if(!error) {   

           [weakSelf.viewsetToast:@"添加自定义表情成功"];

     } 

}]; 


上例中的单例持有的代码块中要用弱引用,原因是:单例不会被释放掉,它会一直持有block,导致该block所在的ViewController释放不掉。 

(3) 如果是方法中的参数是block,不会造成循环引用,因为方法中的block是位于栈内存的,方法返回后,block将会无效。


Cell使用block潜在的循环引用

回到标题说的情况,使用UITableViewCell或者UICollectionViewCell的子类定制cell时,会遇到cell上有个独立的按钮事件需要回调,当使用block来实现这个回调的设计时就会发生一个容易忽略的循环引用,Xcode编译器无法发现这类隐形循环引用,没有警告提醒,如下图没有警告,但循环引用已经产生,当该控制器被pop出去时不会销毁dealloc



分析其原因在于cell实际是tableView的子视图,每个子视图都是会被其父视图的subviewsNSArray *)属性所强引用,即tableView ~> subviews ~> cell,而cell因为使用block作为回调强引用了block内部的对象,形成了这样的循环引用链条,即 controller ~> tableView ~> cell ~> controller,解决的方法同样是使用弱引用传入block,如下图所示。

    __weak typeof(self) weakSelf = self;

    cell.cellBlock = ^(id cell) {

        [weakSelf iLog];

    };


    return cell;

值得注意的是,在这个例子中,即使tableView属性的声明为weak,循环引用仍然会产生,原因在于tableView还是controllerview属性的子视图,强引用链接同样存在,因此最好是在block内部切开强引用链条。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值