美文网首页
Block(二)__block

Block(二)__block

作者: 紫荆秋雪_文 | 来源:发表于2018-07-08 12:07 被阅读6次

    一、__block修饰基本数据类型的局部变量

    1、被__block修饰的局部变量可以在Block内部修改,__block不能修饰全局变量、静态变量(static)

    • __block修饰的变量,可以在Block内部被修改
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    typedef void(^RevanBlock)(void);
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        __block int autoA = 10;
        
        RevanBlock revan_b = ^{
            autoA = 20;
            NSLog(@"局部变量autoA=%d", autoA);
        };
        
        revan_b();
    }
    
    @end
    打印输出:
    2018-07-07 23:53:14.791546+0800 01-Block本质[11691:578111] 局部变量autoA=20
    
    • 源码分析
    • 1、__ViewController__viewDidLoad_block_impl_0结构体,发现被__block修饰的基本数据类型autoA现在是一个__Block_byref_autoA_0类型的指针
    struct __ViewController__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController__viewDidLoad_block_desc_0* Desc;
       //没有__block修饰时 int autoA
      __Block_byref_autoA_0 *autoA; // by ref
      __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_autoA_0 *_autoA, int flags=0) : autoA(_autoA->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 2、__Block_byref_autoA_0结构体如下
    //结构体
    struct __Block_byref_autoA_0 {
      void *__isa;//NSObject对象特有
    __Block_byref_autoA_0 *__forwarding;//这个__forwarding指向了__Block_byref_autoA_0类型的地址
     int __flags;
     int __size;
     int autoA;//真正的autoA
    };
    
    • 3、__block int autoA = 10代码底层源码如下
    __attribute__((__blocks__(byref))) __Block_byref_autoA_0 autoA = {
            (void*)0,
            (__Block_byref_autoA_0 *)&autoA,
            0,
            sizeof(__Block_byref_autoA_0),
            10
        };
    
    • 4、Block代码段
    RevanBlock revan_b =
        ((void (*)())&__ViewController__viewDidLoad_block_impl_0(
                                                                 
                (void *)__ViewController__viewDidLoad_block_func_0,//Block的回调方法
                &__ViewController__viewDidLoad_block_desc_0_DATA,//Block一些信息
                (__Block_byref_autoA_0 *)&autoA,//__block修饰符对象
                570425344)
         );
    
    • 5、__ViewController__viewDidLoad_block_func_0函数,Block回调代码块
    static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
        //Block通过autoA来找到__block对象
      __Block_byref_autoA_0 *autoA = __cself->autoA; // bound by ref
            //__block对象再通过__forwarding找到__block对象具体地址(有可能从栈区复制到堆区),在找到autoA变量直接重新赋值
            (autoA->__forwarding->autoA) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ym_kg_kmbfn17v86dqrzyv26ww80000gn_T_ViewController_2e1ccd_mi_0, (autoA->__forwarding->autoA));
        }
    
    • 6、__ViewController__viewDidLoad_block_desc_0_DATA的结构体类型
    static struct __ViewController__viewDidLoad_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
      void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
    }
    
    • __ViewController__viewDidLoad_block_desc_0_DATA函数内部实现
     __ViewController__viewDidLoad_block_desc_0_DATA = {
        0,
        sizeof(struct __ViewController__viewDidLoad_block_impl_0),
        //当Block从栈区copy到堆区时,自动会触发__ViewController__viewDidLoad_block_copy_0函数
        __ViewController__viewDidLoad_block_copy_0,
        //当Block销毁时,自动触发__ViewController__viewDidLoad_block_dispose_0函数
        __ViewController__viewDidLoad_block_dispose_0
        
    };
    
    • 当Block从栈区copy到堆区时,自动触发__ViewController__viewDidLoad_block_copy_0函数
    static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {
        //调用_Block_object_assign函数,使Block内部强引用__block对象(__Block_byref_autoA_0)
        _Block_object_assign((void*)&dst->autoA, (void*)src->autoA, 8/*BLOCK_FIELD_IS_BYREF*/);
        
    }
    
    • 当Block销毁时,会触发__ViewController__viewDidLoad_block_dispose_0函数,释放对__block对象的强引用
    static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {
        //当Block销毁时,Block对__block对象(__Block_byref_autoA_0)的强引用要release
        _Block_object_dispose((void*)src->autoA, 8/*BLOCK_FIELD_IS_BYREF*/);
        
    }
    
    • 小结:
      • 1、使用了__block修饰的基本类型变量,在Block内部使用时,Block会把这个基本类型变量当成一个对象来捕获到结构体中,这个对象中的成员有一个是这个基本数据类型变量 __block底层数据结构.png
      • 2、当block在栈上时,并不会对__block变量产生强引用
      • 3、当block被copy到堆时
        • 3.1、会调用block内部的copy函数
        • 3.2、copy函数内部会调用_Block_object_assign函数
        • 3.3、_Block_object_assign函数会对__block变量形成强引用 __block修饰的基本数据类型内存管理一.png __block修饰的基本数据类型内存管理二.png __block修饰的基本数据类型内存管理__forwarding.png

    二、__block修饰对象类型的局部变量

    • RevanPerson
    #import <Foundation/Foundation.h>
    
    @interface RevanPerson : NSObject
    
    @end
    
    #import "RevanPerson.h"
    
    @implementation RevanPerson
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    • 测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    typedef void(^RevanBlock)(void);
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        __block RevanPerson *person = [[RevanPerson alloc] init];
        
        RevanBlock revan_b = ^{
            NSLog(@"局部变量autoA=%@", person);
        };
        revan_b();
        NSLog(@"__block修饰对象");
    }
    
    @end
    打印输出
    2018-07-08 01:18:41.131610+0800 01-Block本质[13071:639577] 局部变量autoA=<RevanPerson: 0x600000017a60>
    2018-07-08 01:18:41.131807+0800 01-Block本质[13071:639577] __block修饰对象
    2018-07-08 01:18:41.131922+0800 01-Block本质[13071:639577] -[RevanPerson dealloc]
    
    • 源码分析
    • 1、__ViewController__viewDidLoad_block_impl_0结构体
    struct __ViewController__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController__viewDidLoad_block_desc_0* Desc;
        //__block对象
      __Block_byref_person_0 *person; // by ref
        
      __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 2、__block对象__Block_byref_person_0结构体
    struct __Block_byref_person_0 {
      void *__isa;
    __Block_byref_person_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
        //在__block对象中有关于捕获的对象修饰类型
     RevanPerson *__strong person;
    };
    

    当使用__block 和 __weak一起来修饰对象

    • 测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    typedef void(^RevanBlock)(void);
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        __block __weak RevanPerson *person = [[RevanPerson alloc] init];
        
        RevanBlock revan_b = ^{
            NSLog(@"局部变量autoA=%@", person);
        };
        revan_b();
        NSLog(@"__block修饰对象");
    }
    
    @end
    
    • 源码分析
    • 1、__ViewController__viewDidLoad_block_impl_0结构体
    struct __ViewController__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController__viewDidLoad_block_desc_0* Desc;
        //__block对象
      __Block_byref_person_0 *person; // by ref
      __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 2、__block对象中person对象的修饰是__weak类型的
    struct __Block_byref_person_0 {
      void *__isa;
    __Block_byref_person_0 *__forwarding;
     int __flags;
     int __size;
      //copy用来决定__block对象对person对象是强引用还是弱引用
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
        //使用__weak修饰的对象
     RevanPerson *__weak person;
    };
    
    • 小结:
    • 当block在栈上时
      • 不会产生强引用
    • 当block拷贝到堆上时
      • 会调用__block变量内部的copy函数_Block_object_assign函数会根据所指向对象的修饰符做出相应的操作
      • 如果是基本类型局部变量,__block 对象会强引用
      • 如果是对象类型局部变量,会通过修饰对象有无__weak来决定__block对象对局部对象是强引用还是弱引用
    • 如果__block变量从堆上移除
      • 会调用__block变量内部的dispose函数
      • dispose函数内部会调用_Block_object_dispose函数
      • _Block_object_dispose函数会自动释放指向的对象 __block和__weak修饰对象.png

    二、Block的循环引用

    1、ARC下的Block的循环引用

    • 产生循环引用
    • 1、RevanPerson
    #import <Foundation/Foundation.h>
    
    typedef void(^RevanBlock)(void);
    
    @interface RevanPerson : NSObject
    @property (nonatomic, copy) RevanBlock revan_b;
    @end
    
    #import "RevanPerson.h"
    @implementation RevanPerson
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    • 2、测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        RevanPerson *person = [[RevanPerson alloc] init];
        
        person.revan_b = ^{
            NSLog(@"block中使用对象=%@", person);
        };
        person.revan_b();
        NSLog(@"Block的循环引用");
    }
    
    @end
    打印输出:
    2018-07-08 10:24:58.030126+0800 01-Block本质[14588:705621] block中使用对象=<RevanPerson: 0x604000007f40>
    2018-07-08 10:24:58.030317+0800 01-Block本质[14588:705621] Block的循环引用
    
    • 发现程序已经运行完毕,但是RevanPerson对象并没有释放
    • 源码分析
    struct __ViewController__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController__viewDidLoad_block_desc_0* Desc;
        //block内部有一个person属性,是强引用
      RevanPerson *__strong person;
      __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, RevanPerson *__strong _person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 最开始内存图 使用Block造成循环引用.png
    • 代码运行完,person销毁 使用Block循环引用.png
    • Block造成的循环引用 使用Block循环引用.png

    2、解决循环引用

    2.1、使用__weak修饰person对象,在Block中使用经过__weak修饰的person对象

    • 1、RevanPerson
    #import <Foundation/Foundation.h>
    
    typedef void(^RevanBlock)(void);
    
    @interface RevanPerson : NSObject
    @property (nonatomic, copy) RevanBlock revan_b;
    @end
    
    #import "RevanPerson.h"
    
    @implementation RevanPerson
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    • 2、测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        RevanPerson *person = [[RevanPerson alloc] init];
        __weak typeof(person) weakPerson = person;
        person.revan_b = ^{
            NSLog(@"block中使用对象=%@", weakPerson);
        };
        person.revan_b();
        NSLog(@"Block的循环引用");
    }
    
    @end
    打印输出:
    2018-07-08 10:51:27.219829+0800 01-Block本质[15081:722988] block中使用对象=<RevanPerson: 0x604000206c50>
    2018-07-08 10:51:27.220034+0800 01-Block本质[15081:722988] Block的循环引用
    2018-07-08 10:51:27.220173+0800 01-Block本质[15081:722988] -[RevanPerson dealloc]
    
    • 使用__weak来解决了Block的循环引用
    • 源码分析
    struct __ViewController__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController__viewDidLoad_block_desc_0* Desc;
        //Block中捕获的weakPerson对象是使用__weak修饰
      RevanPerson *__weak weakPerson;
      __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, RevanPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 此时的内存图 Block中使用__weak修饰的对象.png
    • 由于RevanPerson对象没有被强指针引用所以RevanPerson对象会销毁。 没有被任何强引用指向的Block.png
    • 最后Block也销毁

    2.2、使用__unsafe_unretained修饰person对象,在Block中使用经过__unsafe_unretained修饰的person对象

    • 测试代码
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        RevanPerson *person = [[RevanPerson alloc] init];
        __unsafe_unretained typeof(person) weakPerson = person;
        person.revan_b = ^{
            NSLog(@"block中使用对象=%@", weakPerson);
        };
        person.revan_b();
        NSLog(@"Block的循环引用");
    }
    
    @end
    打印输出:
    2018-07-08 11:15:09.196428+0800 01-Block本质[15436:739542] block中使用对象=<RevanPerson: 0x60400000d670>
    2018-07-08 11:15:09.196622+0800 01-Block本质[15436:739542] Block的循环引用
    2018-07-08 11:15:09.196723+0800 01-Block本质[15436:739542] -[RevanPerson dealloc]
    

    2.3、使用__block修饰person对象,在Block中使用经过__block修饰的person对象

    #import "ViewController.h"
    #import "RevanPerson.h"
    
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        __block RevanPerson *person = [[RevanPerson alloc] init];
        person.revan_b = ^{
            NSLog(@"block中使用对象=%@", person);
            person = nil;
        };
        person.revan_b();
        NSLog(@"Block的循环引用");
    }
    
    @end
    打印输出:
    2018-07-08 11:18:03.735457+0800 01-Block本质[15505:742290] block中使用对象=<RevanPerson: 0x604000005590>
    2018-07-08 11:18:03.735674+0800 01-Block本质[15505:742290] Block的循环引用
    2018-07-08 11:18:03.735842+0800 01-Block本质[15505:742290] -[RevanPerson dealloc]
    
    • 源码分析
    • __ViewController__viewDidLoad_block_impl_0结构体
    struct __ViewController__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController__viewDidLoad_block_desc_0* Desc;
        //使用__block修饰的weakPerson被包装一个__Block_byref_weakPerson_0类型的对象
      __Block_byref_person_0 *person; // by ref
      __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • __block包装的对象__Block_byref_person_0结构体
    struct __Block_byref_person_0 {
      void *__isa;
    __Block_byref_person_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
        //__block构造的对象强引用person
     RevanPerson *__strong person;
    };
    
    • 内存图 __block修饰对象造成循环引用.png
    • Block内部执行person = nil;后的内存图 __block修饰的对象在Block中执行person=nil.png
    • 小结
      • 推荐使用__weak,当释放对象后__weak会自动把曾经指向对象的指针赋值为nil
      • __unsafe_unretained弊端在于释放对象后不会吧曾经指向对象的指针自动赋值为nil,这样会造成野指针访问
      • __block的弊端在于要在Block回调方法中手动给对象赋值为nil,并且必须要执行这个Block,否则会存在内存泄露

    3、MRC下,Block的循环引用

    3.1、循环引用

    • 1、RevanPerson
    #import <Foundation/Foundation.h>
    
    typedef void(^RevanBlock)(void);
    
    @interface RevanPerson : NSObject
    @property (nonatomic, copy) RevanBlock revan_b;
    @end
    
    #import "RevanPerson.h"
    
    @implementation RevanPerson
    
    - (void)dealloc {
        [super dealloc];
        NSLog(@"%s", __func__);
    }
    
    @end
    
    • 2、测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        RevanPerson *person = [[RevanPerson alloc] init];
        person.revan_b = ^{
            NSLog(@"block中使用对象=%@", person);
        };
        
        person.revan_b();
        [person release];
        NSLog(@"MRC Block的循环引用");
    }
    
    @end
    打印输出:
    2018-07-08 11:51:02.246755+0800 01-Block本质[16083:766749] block中使用对象=<RevanPerson: 0x600000206be0>
    2018-07-08 11:51:02.246974+0800 01-Block本质[16083:766749] MRC Block的循环引用
    

    3.2、解决Block循环引用

    3.2.1、__unsafe_unretained修饰对象

    • 测试代码
    tation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        __unsafe_unretained RevanPerson *person = [[RevanPerson alloc] init];
        person.revan_b = ^{
            NSLog(@"block中使用对象=%@", person);
        };
        
        person.revan_b();
        [person release];
        NSLog(@"MRC Block的循环引用");
    }
    
    @end
    打印输出:
    2018-07-08 11:58:07.785725+0800 01-Block本质[16252:773066] block中使用对象=<RevanPerson: 0x60400000b8e0>
    2018-07-08 11:58:07.785945+0800 01-Block本质[16252:773066] -[RevanPerson dealloc]
    2018-07-08 11:58:07.786089+0800 01-Block本质[16252:773066] MRC Block的循环引用
    
    • 原理和ARC下一样

    3.2.2、__block修饰对象

    • 测试代码
    #import "ViewController.h"
    #import "RevanPerson.h"
    
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        __block RevanPerson *person = [[RevanPerson alloc] init];
        person.revan_b = ^{
            NSLog(@"block中使用对象=%@", person);
        };
        
        person.revan_b();
        [person release];
        NSLog(@"MRC Block的循环引用");
    }
    
    @end
    打印输出:
    2018-07-08 12:00:22.196453+0800 01-Block本质[16323:775301] block中使用对象=<RevanPerson: 0x60000001a160>
    2018-07-08 12:00:22.196673+0800 01-Block本质[16323:775301] -[RevanPerson dealloc]
    2018-07-08 12:00:22.197707+0800 01-Block本质[16323:775301] MRC Block的循环引用
    
    • 在MRC下使用__block时,对对象包装成的__Block对象不会对对象(person)产生强引用,所以不会造成循环引用。

    相关文章

      网友评论

          本文标题:Block(二)__block

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