iOS-Block 浅谈

作者: 梦蕊dream | 来源:发表于2018-05-17 14:17 被阅读50次

    前言:Block 是开发过程中常用便捷的回调方式,本文简单介绍 Block

    一、Block 简介

    Block 对象是 C 级别的语法和运行时特性,和标准的 C 函数类似,除了可执行代码外,还可能包含变量自动绑定(栈)和内存托管(堆)。一个 Block 维护一个状态集。
    闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」;Block 是 Objective-C 对于闭包的实现。

    • 可以嵌套定义,定义 Block 方法和定义函数方法相似
    • Block 可以定义在方法内部或外部
    • 只有调用 Block 时候,才会执行其{}体内的代码
    • 本质是对象,使代码高聚合

    使用 clang 将 OC 代码转换为 C++ 文件查看 block 的方法:

    • 在命令行输入代码 clang -rewrite-objc 需要编译的OC文件.m
    • 这时查看当前的文件夹里 多了一个相同的名称的 .cpp 文件,在命令行输入 open main.cpp 查看文件

    1.1 定义和使用

    1.无参数无返回值

    void (^ MyBlockOne)(void) = ^(void){
        NSLog(@"无参数,无返回值");  
    };  
    MyBlockOne();//block的调用
    

    2.有参数无返回值

    void(^MyblockTwo)(int a) = ^(int a){
        NSLog(@"@ = %d我就是block,有参数,无返回值",a);
    };  
    MyblockTwo(100);
    

    3.有参数有返回值

    int(^MyBlockThree)(int,int) = ^(int a,int b){    
        NSLog(@"%d我就是block,有参数,有返回值",a + b);returna + b; 
    };  
    MyBlockThree(12,56);
    

    4.无参数有返回值

    int(^MyblockFour)(void) = ^{NSLog(@"无参数,有返回值");
            return45;
      };
    MyblockFour();
    

    5.定义声明

    声明

    typedef void (^Block)();
    typedef int (^MyBlock)(int , int);
    typedef void(^ConfirmBlock)(BOOL isOK);
    typedef void(^AlertBlock)(NSInteger alertTag);
    

    定义属性

    @property (nonatomic,copy) MyBlock myBlockOne;
    
    

    使用

    self.myBlockOne = ^int (int ,int){
        //TODO
    }
    

    1.2 Block与外界变量

    1、截获自动变量(局部变量)值

    (1)默认情况
    对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量, 不使用则不截获, 因为截获的自动变量会存储于block的结构体内部, 会导致block体积变大。特别要注意的是默认情况下block只能访问不能修改局部变量的值。

    int age = 10;
    myBlock block = ^{
        NSLog(@"age = %d", age);
    };
    age = 18;
    block();
    

    输出结果:
    age = 10

    (2) __block 修饰的外部变量

    对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。block可以修改__block 修饰的外部变量的值。

    __block int age = 10;
    myBlock block = ^{
        NSLog(@"age = %d", age);
    };
    age = 18;
    block();
    

    输出为:
    age = 18

    2、__block 修饰的外部变量的值就可以被block修改

    我们使用 clang 将 OC 代码转换为 C++ 文件:

    clang -rewrite-objc 源代码文件名
    
    __block int val = 10;
    转换成
    __Block_byref_val_0 val = {
        0,
        &val,
        0,
        sizeof(__Block_byref_val_0),
        10
    };
    

    会发现一个局部变量加上__block修饰符后竟然跟block一样变成了一个__Block_byref_val_0结构体类型的自动变量实例。
    此时我们在block内部访问val变量则需要通过一个叫__forwarding的成员变量来间接访问val变量。

    1.3 Block 存储

    1、Block的存储域及copy操作

    由C/C++/OBJC编译的程序占用内存分布的结构:


    内存分布结构

    block有三种类型:

    • 全局块(_NSConcreteGlobalBlock)
    • 栈块(_NSConcreteStackBlock)
    • 堆块(_NSConcreteMallocBlock)

    三种block各自的存储域:

    • 全局块存在于全局内存中, 相当于单例.
    • 栈块存在于栈内存中, 超出其作用域则马上被销毁
    • 堆块存在于堆内存中, 是一个带引用计数的对象, 需要自行管理其内存
    block存储域

    简而言之,存储在栈中的Block就是栈块、存储在堆中的就是堆块、既不在栈中也不在堆中的块就是全局块。

    1.4 判断Block的存储位置

    (1)Block不访问外界变量(包括栈中和堆中的变量)
    Block 既不在栈又不在堆中,在代码段中,ARC和MRC下都是如此。此时为全局块。
    (2)Block访问外界变量
    MRC 环境下:访问外界变量的 Block 默认存储中。
    ARC 环境下:访问外界变量的 Block 默认存储在中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。

    ARC下,访问外界变量的 Block为什么要自动从栈区拷贝到堆区呢?
    栈上的Block,如果其所属的变量作用域结束,该Block就被废弃,如同一般的自动变量。当然,Block中的__block变量也同时被废弃。
    为了解决栈块在其变量作用域结束之后被废弃(释放)的问题,我们需要把Block复制到堆中,延长其生命周期。开启ARC时,大多数情况下编译器会恰当地进行判断是否有需要将Block从栈复制到堆,如果有,自动生成将Block从栈上复制到堆上的代码。Block的复制操作执行的是copy实例方法。Block只要调用了copy方法,栈块就会变成堆块。

    栈块copy堆块
    • 在ARC的Block是配置在栈上的,所以返回函数调用方时,Block变量作用域就结束了,Block会被废弃。种情况编译器会自动完成复制。
    • 在非ARC情况下则需要开发者调用copy方法手动复制。
    • 将Block从栈上复制到堆上相当消耗CPU,所以当Block设置在栈上也能够使用时,就不要复制了,因为此时的复制只是在浪费CPU资源。

    Block的复制操作执行的是copy实例方法。不同类型的Block使用copy方法的效果如下表:

    Block的复制操作

    根据表得知,Block在堆中copy会造成引用计数增加,这与其他Objective-C对象是一样的。虽然Block在栈中也是以对象的身份存在,但是栈块没有引用计数,因为不需要,我们都知道栈区的内存由编译器自动分配释放。
    不管Block存储域在何处,用copy方法复制都不会引起任何问题。在不确定时调用copy方法即可。在ARC有效时,多次调用copy方法完全没有问题:

    blk = [[[[blk copy] copy] copy] copy];
    // 经过多次复制,变量blk仍然持有Block的强引用,该Block不会被废弃。
    
    1.5 __block变量与__forwarding

    在copy操作之后,既然__block变量也被copy到堆上去了, 那么访问该变量是访问栈上的还是堆上的呢?__forwarding 终于要闪亮登场了。通过__forwarding, 无论是在block中还是 block外访问__block变量, 也不管该变量在栈上或堆上, 都能顺利地访问同一个__block变量。

    __block与__forwarding

    1.6 Block 循环引用

    Block 循环引用的情况:
    某个类将 block 作为自己的属性变量,然后该类在 block 的方法体里面又使用了该类本身。

    self.someBlock = ^(Type var){
        [self dosomething];
    };
    

    解决办法:
    (1)ARC 下:使用 __weak

    __weak typeof(self) weakSelf = self;
    self.someBlock = ^(Type var){
       [weakSelf dosomething];
    };
    

    (2)MRC 下:使用 __block

    __block typeof(self) blockSelf = self;
    self.someBlock = ^(Type var){
       [blockSelf dosomething];
    };
    

    解决办法:

    //1.使用__weak ClassName
        __block XXViewController* weakSelf = self;
        self.blk = ^{
            NSLog(@"In Block : %@",weakSelf);
        };
    //2.使用__weak typeof(self)
        __weak typeof(self) weakSelf = self;
        self.blk = ^{
            NSLog(@"In Block : %@",weakSelf);
        };
    //3.Reactive Cocoa中的@weakify和@strongify
        @weakify(self);
        self.blk = ^{
            @strongify(self);
            NSLog(@"In Block : %@",self);
        };
    

    二、Block 应用

    2.1 Block 应用

    1、Block作为变量(Xcode快捷键:inlineBlock)

    int (^sum) (int, int); // 定义一个 Block 变量 sum
    // 给 Block 变量赋值
    // 一般 返回值省略:sum = ^(int a,int b)…
    sum = ^int (int a,int b){  
        return a+b;
    }; // 赋值语句最后有 分号
    int a = sum(10,20); // 调用 Block 变量
    

    2、Block作为属性(Xcode 快捷键:typedefBlock)

    // 1. 给  Calculate 类型 sum变量 赋值「下定义」
    typedef int (^Calculate)(int, int); // calculate就是类型名
    Calculate sum = ^(int a,int b){ 
        return a+b;
    };
    int a = sum(10,20); // 调用 sum变量
    
    // 2. 作为对象的属性声明,copy 后 block 会转移到堆中和对象一起
    @property (nonatomic, copy) Calculate sum;    // 使用   typedef
    @property (nonatomic, copy) int (^sum)(int, int); // 不使用 typedef
    
    // 声明,类外
    self.sum = ^(int a,int b){
        return a+b;
    };
    // 调用,类内
    int a = self.sum(10,20);
    

    3、作为 OC 中的方法参数

    // ---- 无参数传递的 Block ---------------------------
    // 实现
    - (CGFloat)testTimeConsume:(void(^)())middleBlock {
        // 执行前记录下当前的时间
        CFTimeInterval startTime = CACurrentMediaTime();
        middleBlock();
        // 执行后记录下当前的时间
        CFTimeInterval endTime = CACurrentMediaTime();
        return endTime - startTime;
    
    }
    
    // 调用
    [self testTimeConsume:^{
           // 放入 block 中的代码 
    
    }];
    
    // ---- 有参数传递的 Block ---------------------------
    // 实现
    - (CGFloat)testTimeConsume:(void(^)(NSString * name))middleBlock {
        // 执行前记录下当前的时间
        CFTimeInterval startTime = CACurrentMediaTime();
        NSString *name = @"有参数";
        middleBlock(name);
        // 执行后记录下当前的时间
        CFTimeInterval endTime = CACurrentMediaTime();
        return endTime - startTime;
    }
    
    // 调用
    [self testTimeConsume:^(NSString *name) {
       // 放入 block 中的代码,可以使用参数 name
       // 参数 name 是实现代码中传入的,在调用时只能使用,不能传值    
    
    }];
    

    4、Block回调
    Block回调是关于Block最常用的内容,比如网络下载,我们可以用Block实现下载成功与失败的反馈。block使用简单,逻辑清晰,灵活。

    2.2 Block 几种类型演算

        {
            NSLog(@"\n--------------------block调用 基本数据类型---------------------\n");
            int a = 10;
            NSLog(@"block定义前a地址=%p", &a);
            void (^aBlock)() = ^(){
                NSLog(@"block定义内部a地址=%p", &a);
            };
            NSLog(@"block定义后a地址=%p", &a);
            aBlock();
        }
         
        /*
         结果:
         block定义前a地址=0x7fff5bdcea8c
         block定义后a地址=0x7fff5bdcea8c
         block定义内部a地址=0x7fa87150b850
         */
         
        /*
         流程:
         1. block定义前:a在栈区
         2. block定义内部:里面的a是根据外面的a拷贝到堆中的,不是一个a
         3. block定义后:a在栈区
         */
         
        {
            NSLog(@"\n--------------------block调用 __block修饰的基本数据类型---------------------\n");
             
            __block int b = 10;
            NSLog(@"block定义前b地址=%p", &b);
            void (^bBlock)() = ^(){
                b = 20;
                NSLog(@"block定义内部b地址=%p", &b);
            };
            NSLog(@"block定义后b地址=%p", &b);
            NSLog(@"调用block前 b=%d", b);
            bBlock();
            NSLog(@"调用block后 b=%d", b);
        }
         
        /*
         结果:
         block定义前b地址=0x7fff5bdcea50
         block定义后b地址=0x7fa873b016d8
         调用block前 b=10
         block定义内部b地址=0x7fa873b016d8
         调用block后 b=20
         */
         
        /*
         流程:
         1. 声明 b 为 __block (__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。)
         2. block定义前:b在栈中。
         3. block定义内部: 将外面的b拷贝到堆中,并且使外面的b和里面的b是一个。
         4. block定义后:外面的b和里面的b是一个。
         5. block调用前:b的值还未被修改。
         6. block调用后:b的值在block内部被修改。
         */
         
        {
            NSLog(@"\n--------------------block调用 指针---------------------\n");
             
            NSString *c = @"ccc";
            NSLog(@"block定义前:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
            void (^cBlock)() = ^{
                NSLog(@"block定义内部:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
            };
            NSLog(@"block定义后:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
            cBlock();
            NSLog(@"block调用后:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
        }
         
        /* 输出结果
          block定义前:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
          block定义后:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
          block定义内部:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x6000002542a0
          block调用后:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
         c指针本身在block定义中和外面不是一个,但是c指向的地址一直保持不变。
         1. block定义前:c指向的地址在堆中, c指针本身的地址在栈中。
         2. block定义内部:c指向的地址在堆中, c指针本身的地址在堆中(c指针本身和外面的不是一个,但是指向的地址和外面指向的地址是一样的)。
         3. block定义后:c不变,c指向的地址在堆中, c指针本身的地址在栈中。
         4. block调用后:c不变,c指向的地址在堆中, c指针本身的地址在栈中。
         */
        {
            NSLog(@"\n--------------------block调用 指针并修改值---------------------\n");
             
            NSMutableString *d = [NSMutableString stringWithFormat:@"ddd"];
            NSLog(@"block定义前:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
            void (^dBlock)() = ^{
                NSLog(@"block定义内部:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
                d.string = @"dddddd";
            };
            NSLog(@"block定义后:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
            dBlock();
            NSLog(@"block调用后:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
        }
         
        /*输出结果
         block定义前:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8
         block定义后:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8
         block定义内部:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x604000253940
         block调用后:d=dddddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8
    
         d指针本身在block定义中和外面不是一个,但是d指向的地址一直保持不变。
         在block调用后,d指向的堆中存储的值发生了变化。
         */
         
        {
            NSLog(@"\n--------------------block调用 __block修饰的指针---------------------\n");
             
            __block NSMutableString *e = [NSMutableString stringWithFormat:@"eee"];
            NSLog(@"block定义前:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
            void (^eBlock)() = ^{
                NSLog(@"block定义内部:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
                e = [NSMutableString stringWithFormat:@"new-eeeeee"];
            };
            NSLog(@"block定义后:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
            eBlock();
            NSLog(@"block调用后:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
        }
         
        /*
         从block定义内部使用__block修饰的e指针开始,e指针本身的地址由栈中改变到堆中,即使出了block,也在堆中。
         在block调用后,e在block内部重新指向一个新对象,e指向的堆中的地址发生了变化。
         */
         
        {
            NSLog(@"\n--------------------block调用 retain cycle---------------------\n");
             
            View *v = [[View alloc] init];
            v.tag = 1;
            v.frame = CGRectMake(100, 100, 100, 100);
            [self.view addSubview:v];      //self->view->v
            void (^block)() = ^{
                v.backgroundColor = [UIColor orangeColor]; //定义内部:block->v
            };
            v.block = block;    //v->block
            block();   
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                //预计3秒后释放v对象。
                [v removeFromSuperview];
            });
        }
         
        /*
         结果:
         不会输出 dealloc.
         */
         
        /*
         流程:
         1. self->view->v
         2. block定义内部:block->v 因为block定义里面调用了v
         3. v->block
          
         结论:
         引起循环引用的是block->v->block,切断其中一个线即可解决循环引用,跟self->view->v这根线无关
         */
         
        {
            NSLog(@"\n--------------------block调用self---------------------\n");
             
            View *v = [[View alloc] init];
            v.tag = 2;
            v.frame = CGRectMake(100, 220, 100, 100);
            [self.view addSubview:v];      //self->view->v
            void (^block)() = ^{
                self.view.backgroundColor = [UIColor redColor]; //定义内部:block->self
                _count ++;   //调用self的实例变量,也会让block强引用self。
                 
            };
            v.block = block;    //v->block
            block();
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                //预计3秒后释放self这个对象。
                AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
                appDelegate.window.rootViewController = nil;
            });
        }
        /*
         结果:
         不会输出 dealloc.
         */
         
        /*
         流程:
         1. self->view->v
         2. v->block
         3. block->self 因为block定义里面调用了self
          
         结论:
         在block内引用实例变量,该实例变量会被block强引用。
         引起循环引用的是self->view->v->block->self,切断一个线即可解决循环引用。
         */
    

    2.3 Block 存储域

    1. 在全局数据区的Block对象

    NSGlobalBlock 静态block,释放有两种不同的时机:

    • 1、如果这个block引用了外部变量后是栈block,则在定义此block的函数出栈时,block释放。
    • 2、如果这个blcok引用了外部变量之后是堆block,则其宿主target释放的时候此block才释放。
        {
            NSLog(@"\n--------------------block的存储域 全局块---------------------\n");
             
            void (^blk)(void) = ^{
                NSLog(@"Global Block");
            };
            blk();
            NSLog(@"%@", [blk class]);
        }
        /*
         结果:输出 __NSGlobalBlock__
         */
         
        /*
         结论:
         全局块:这种块不会捕捉任何状态(外部的变量),运行时也无须有状态来参与。块所使用的整个内存区域,在编译期就已经确定。
         全局块一般声明在全局作用域中。但注意有种特殊情况,在函数栈上创建的block,如果没有捕捉外部变量,block的实例还是会被设置在程序的全局数据区,而非栈上。
         */
    
    2.在堆上创建的Block对象

    NSMallocBlock 堆区block
    堆区是内存的常驻区域,也叫永久存储区,block一般在函数中定义,最多是个栈block。在MRC时代你需要使用Block_copy()方法,才可以将blcok复制到堆中。

    复制到堆中有何用处呢?

    • 首先,作为一个对象,把它复制到堆中,想要使用它肯定要有一个指针指向它,而指向它的指针是作为property或静态变量出现的(如果不被引用也就没有了常驻于堆区的意义),而实际开发中多使用poperty引用。
    • 在MRC中,如果一个类的block属性是使用copy修饰的,则不需要手动调用Block_copy将其复制到堆中。如果是用strong修饰的,则必须使用Block_copy()将其复制到堆中,并在释放时调用Block_release()方法将其释放,否则会因野指针导致程序崩溃。
        {
            NSLog(@"\n--------------------block的存储域 堆块---------------------\n");
             
            int i = 1;
            void (^blk)(void) = ^{
                NSLog(@"Malloc Block, %d", i);
            };
            blk();
            NSLog(@"%@", [blk class]);
        }
        /*
         结果:输出 __NSMallocBlock__
         */
         
        /*
         结论:
         堆块:解决块在栈上会被覆写的问题,可以给块对象发送copy消息将它拷贝到堆上。复制到堆上后,块就成了带引用计数的对象了。
          
         在ARC中,以下几种情况栈上的Block会自动复制到堆上:
         - 调用Block的copy方法
         - 将Block作为函数返回值时(MRC时此条无效,需手动调用copy)
         - 将Block赋值给__strong修饰的变量时(MRC时此条无效)
         - 向Cocoa框架含有usingBlock的方法或者GCD的API传递Block参数时
          
         上述代码就是在ARC中,block赋值给__strong修饰的变量,并且捕获了外部变量,block就会自动复制到堆上。
         */
    
    3.在栈上创建的Block对象

    NSStackBlock 栈区block

    • 函数只有入栈后才能执行,出栈后就释放了。
    • 栈block一般在函数内部定义,并在函数内部调用;或者在函数外部定义,作为函数的一个参数在函数内部调用。函数出栈时和其他变量或参数一起释放。
        {
            NSLog(@"\n--------------------block的存储域 栈块---------------------\n");
            int i = 1;
            __weak void (^blk)(void) = ^{
                NSLog(@"Stack Block, %d", i);
            };
            blk();
            NSLog(@"%@", [blk class]);
        }
        /*
         结果:输出 __NSStackBlock__
         */
         
        /*
         结论:
         栈块:块所占内存区域分配在栈中,编译器有可能把分配给块的内存覆写掉。
         在ARC中,除了上面四种情况,并且不在global上,block是在栈中。
         */
    

    三、Block 原理

    Block 优缺点

    优点:

    • 捕获外部变量
    • 降低代码分散程度

    缺点:

    • 循环引用引起内存泄露

    Block 总结

    • 在block内部使用的是将外部变量的拷贝到堆中的(基本数据类型直接拷贝一份到堆中,对象类型只将在栈中的指针拷贝到堆中并且指针所指向的地址不变)。
    • __block修饰符的作用:是将block中用到的变量,拷贝到堆中,并且外部的变量本身地址也改变到堆中。
    • __block不能解决循环引用,需要在block执行尾部将变量设置成nil
    • __weak可以解决循环引用,block在捕获weakObj时,会对weakObj指向的对象进行弱引用。
    • 使用__weak时,可在block开始用局部__strong变量持有,以免block执行期间对象被释放。
    • 全局块不引用外部变量,所以不用考虑。
    • 堆块引用的外部变量,不是原始的外部变量,是拷贝到堆中的副本。
    • 栈块本身就在栈中,引用外部变量不会拷贝到堆中。
    • __weak 本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。
    • __block 本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。另外一点就是 __block 修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。
    • block的实现原理是C语言的函数指针。函数指针即函数在内存中的地址,通过这个地址可以达到调用函数的目的。

    Q:什么是Block?
    A:Block是将函数及其执行上下文封装起来的对象

    struct __block_impl{
      void *isa;//Block 是对象的标志
      int Flags;
      int Reserved;
      void *FuncPtr;//函数指针
    };
    

    Q:什么是Block调用?
    A:Block调用是函数调用

    Q:Block 如何截获变量?
    A:1.基本数据类型的局部变量截获其值
    2.对象类型的局部变量连同所有权修饰符一起截获
    3.局部静态变量指针形式截获
    4.不截获全局变量、静态全局变量

    Q:什么情况使用__block修饰符?
    A:一般情况下,对被截获变量进行赋值操作需要添加__block修饰符
    赋值 != 使用
    赋值:
    赋值操作需要使用 __block修饰

    __block NSMutableArray *arrM = nil;
        void (^testBlock)(void) = ^{
            arrM = [NSMutableArray array];
        };
        testBlock();
    

    使用:如下代码不需要使用__block,因为是对数组的操作而不是数组的赋值。

    NSMutableArray *arrM = [NSMutableArray array];
        void (^testBlock)(void) = ^{
            [arrM addObject:@"addObj"];
        };
        testBlock();
    
    需要修饰符
    不需要修饰符

    Q:__block做了什么
    A:__block修饰变量变成了对象

    __block修饰符

    Q:Block的Copy操作效果
    A:如图

    Block的Copy操作效果

    Q:栈上Block的销毁
    A:如图

    栈上Block的销毁

    Q:栈上Block的Copy操作
    A:如图
    Q:栈上Block的Copy,MRC是否会引起内存泄漏
    A:会的

    i栈上Block的Copy

    Q:栈上__block变量的Copy操作
    A:如图,修改__block变量值,修改的都是堆上的值

    栈上__block变量的Copy操作

    Q:__forwarding存在的意义
    A:无论在栈还是堆上,__forwarding都可以顺利访问到同一个__block变量

    Q:Block外部定义__weak修饰变量可以解决循环引用?
    A:Block截获对象连同所有权修饰符一起截获的,如在外部对变量进行__weak修饰,结构体里持有的对象类型也是weak

    Q:Block的引用循环,如图代码出现什么问题?

    A:MRC下,不会产生循环引用;ARC下回产生循环引用,引起内存泄漏
    ARC下的引用循环

    ARC下的引用循环
    ARC下的引用循环解决方案
    解决方案

    上述代码:ARC下解决方案
    弊端:如果该Block长时间不被引用,该断环处一直存在,循环引用无法解除

    解决方案

    Question1:__weak修饰对象,当外部对象释放了之后,block 内部也访问不到这个对象,怎么办?

    Answer:通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。

    __weak typeof(self) weakSelf = self;
    self.block = ^{
          __strong typeof(weakSelf) strongSelf = weakSelf;
          [strongSelf print];
    };
    

    我们以 AFNetworking 中 AFNetworkReachabilityManager.m 的一段代码举例:

    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;
    
        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }
    };
    

    Question2:__strong修饰对象,会不会引起循环引用?

    Answer:不会!

    详解:

    __weak修饰的对象被Block引用,不会影响对象的释放,而__strong在Block内部修饰的对象,会保证,在使用这个对象在scope内,这个对象都不会被释放,出了scope,引用计数就会-1。
    self是一个指向实例对象的指针,它的生命周期至少是伴随着当前的实例对象的,一旦它和对象之间有循环引用是无法被自动打破的;strongSelf是block内部的一个局部变量,变量的作用域仅限于局部代码,而程序一旦跳出作用域,strongSelf就会被释放,这个临时产生的“循环引用”就会被自动打破,代码的执行事实上也是这样子的。

    推荐阅读 https://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/

    深入篇:iOS-Block本质

    相关文章

      网友评论

        本文标题:iOS-Block 浅谈

        本文链接:https://www.haomeiwen.com/subject/sekbdftx.html