美文网首页
iOS OC Block详解

iOS OC Block详解

作者: _菩提本无树_ | 来源:发表于2020-03-18 13:47 被阅读0次

    全文分两部分(认认真真看完,肯定有收获)

    1.Block定义主要是介绍一下block的使用
    2.详解Block,深入了解Block的用法

    一.Block定义

    1.Block格式

    //等号左侧是声明一个block,右侧是定义一个block
    (1).定义block块必须以^开头,声明块以返回值类型开头
    (2).定义块的返回值类型可以省略,而且经常会省略,声明不能省略
    (3).定义块无需指定名字,声明块必须得有
    (4).如果块没有参数,参数部分可以省略但是参数部分的括号不能省略,
    但是内部可以留空,通常建议使用void作为占位符
    
    returnType(^blockName)(parameterTypes) = ^(parameters) {
        statements
    };
    
    void(^blockName)(NSString *) = ^void(NSString*str){
        NSLog(@"%@",str);
    };
    blockName(@"这是一个简单的无返回值的block");
    
    //^告诉计算机这是一个block
    NSString *(^blockParam)(NSString *) = ^NSString*(NSString*n){
        NSLog(@"%@",n);
        return @"123456789";
    };
    NSString * str = blockParam(@"这是一个简单的带返回值的block");
    str = @"123456789";
    

    2.Block作为属性

    @property(nonatomic,copy) void (^propertyBlock)(NSString*);
    

    3.typedef定义Block

    typedef void (^typedefBlock)(NSString *);
    @property(nonatomic, copy)typedefBlock block;
    

    4.Block作为参数不带返回值

    - (void)viewDidLoad{
        
        [self blockAsParam:^(NSString *name) {
            NSLog(@"%@",name);//123123123
        }];
    }
    - (void)blockAsParam:(void (^)(NSString * _Nullable))paramBlock{
        paramBlock(@"123123123");
    }
    

    5.Block作为参数带返回值

    - (void)viewDidLoad{
    
        [self blockAsParamReturnValue:^NSString*(NSString *name){
            NSLog(@"%@",name);
            return @"123123123123";
        }];
    
    }
    
    - (void)blockAsParamReturnValue:(NSString *(^)(NSString *str))returnValue{
    
        NSString *s = returnValue(@"123123123");
        NSLog(@"%@",s);//123123123123
    
    }
    

    6.Block不带返回值,作为返回值

    - (void)viewDidLoad{
    
        void(^blockNoReturn)(NSString*) = [self blockNoReturnValue:@"11111"];
        blockNoReturn(@"000000");
    }
    
    - (void(^)(NSString *))blockNoReturnValue:(NSString*)value{
    
        return ^(NSString *name){
            NSLog(@"%@,%@",name,value);//000000,11111
        };
    
    }
    

    7.Block带返回值,作为返回值

    - (void)viewDidLoad{
    
        NSString *(^haveReturnValue)(NSString *) = [self blockHaveReturnValue:@"2222"];
        NSString *s = haveReturnValue(@"123");
        NSLog(@"%@",s);//11111111111111111111
    
    }
    
    - (NSString *(^)(NSString*))blockHaveReturnValue:(NSString*)value{
    
        return ^NSString *(NSString*n){
            NSLog(@"%@,%@",n,value);//123,2222
            return @"11111111111111111111"
        };
    
    }
    

    二.Block详解(2020.03.18更新)

    1.使用copy和strong修饰block有什么区别吗?

    block有三种存储类型__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__.
    
    经测试在MRC环境下,任何位置定义的block,未引用外部变量的情况下为NSGlobalBlock类型.
    引用了外部静态变量(全局/局部)的为NSGlobalBlock,引用了外部非静态变量(全局/局部)的,
    如果使用copy修饰了属性并且使用了self.语法调用的为NSMallocBlock,否则是NSStackBlock类型.
    (
    举例
    @property (nonatomic ,copy) void (^copyPropertyBlock)(NSString * _Nullable);
       
        self.copyPropertyBlock = ^(NSString *c){
            int d = globaln + 9;
        };//globaln为非静态变量,这个时候copyPropertyBlock类型是NSMallocBlock
    )
    在以上的基础上对所有的block执行copy操作,NSGlobalBlock的block保持原状,
    NSStackBlock类型的block变为NSMallocBlock,拷贝到了堆上.所以在MRC中需要使用copy修饰
    否则在栈中会随时释放
    
    在ARC环境下任何位置定义的block,未引用外部变量的情况下为NSGlobalBlock类型,
    引用了外部静态变量(全局/局部)的为NSGlobalBlock,引用了外部非静态变量(全局/局部)的为NSMallocBlock
    在以上的基础上对所有的block执行copy操作,NSGlobalBlock的block保持原状,
    NSMallocBlock的类型未变化(地址也未发生变化)
    
    结论
    在MRC中访问外部非静态变量的block必须需要使用copy对block进行修饰,将其拷贝到堆中.
    ARC中系统已经帮我们将其从栈拷贝到堆了,所以我们不需要在进行copy处理
    ,使用copy或者strong修饰都可以.
    
    网上一搜好多关于这方面的讲解,但是大多数都是复制的一篇文章,内容都一样,
    而且还是比较老的文章,没有涉及ARC和MRC的对比,当然也有好的文章,建议大家自己试一下才会知道结果.
    

    2.__weak和__block

    __weak仅仅是将该对象赋值给weak修饰的对象,__weak还有一个作用就是将被修饰的对象加入缓存池
    中,当缓存池释放时,会对其内部的对象release一次,因为对象是weak修饰的弱引用,所以会被释放.
    不会引起循环引用的问题
    
    __block
    修饰的局部变量会包装成一个结构体对象,这个结构体对象里面包含着被修饰的对象,被block持有,
    这个时候__block修饰的值是可以修改的.
    

    3.block捕获变量
    源码查看方式

    #include "DDBlockStudy.h"
    //全局的
    int w = 23;
    static int e = 10;
    int f[] = {11,22,33,44};
    
    void buildBlock(){
        
        int m = 1;
        static int n = 3;
        int arr[] = {1,2,3,4,5,6};
        
        void (^testBlock)(int)= ^(int d){
            printf("\n%d,%d,%d,%d,%d\n", w,e,*f,m,n);
            //打印的结果是12,14,32,1,10
        };
        w = 12;
        e = 14;
        *f = 32;
        m = 4;
        n = 10;
        testBlock(3);
        
    }
    上面的内容出现的结果可以总结一下
    (1).静态变量(全局和局部)和全局变量并未捕获任何内容,用的时候直接取得,可以修改值
    (2).属性值和成员变量捕获的是对象的持有者(self),可以修改值
    (3).对于局部变量block捕获的是值.
    理由见 static void __buildBlock_block_func_0(struct __buildBlock_block_impl_0 *__cself, int d);函数
    (补充,更确切的说是在__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n)函数中可以看出,详细见下面的👇Block修改变量有解说)
    
    
    
    //通过源码分析
    int w = 23;
    static int e = 10;
    int f[] = {11,22,33,44};
    
    //这是block的定义来自__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n)
    struct __buildBlock_block_impl_0 {
      struct __block_impl impl;
      struct __buildBlock_block_desc_0* Desc;
      //下面的内容是被捕获的参数
      int m;
      int *n;
    
      //void *fp 对应 (void *)__buildBlock_block_func_0 方法的指针
      //struct __buildBlock_block_desc_0 *desc 对应  &__buildBlock_block_desc_0_DATA 这个block的一些参数
      //impl.isa = &_NSConcreteStackBlock;可以得出当前的block在栈中
      //m(_m)将_m的值赋给m,n同理
    
      //从这里看出(int _m, int *_n)这个时候m和n被捕获了,但是m只是值被持有了,n被持有的是指针.  
      __buildBlock_block_impl_0(void *fp, struct __buildBlock_block_desc_0 *desc, int _m, int *_n, int flags=0) : m(_m), n(_n) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    //下面的这个函数存储的是block执行的代码
    static void __buildBlock_block_func_0(struct __buildBlock_block_impl_0 *__cself, int d) {
      int m = __cself->m; // bound by copy
      int *n = __cself->n; // bound by copy
    
            printf("\n%d,%d,%d,%d,%d\n", w,e,*f,m,(*n));
    }
    
    static struct __buildBlock_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __buildBlock_block_desc_0_DATA = { 0, sizeof(struct __buildBlock_block_impl_0)};
    void buildBlock(){
    
        int m = 1;
        static int n = 3;
        int arr[] = {1,2,3,4,5,6};
    
        void (*testBlock)(int) = ((void (*)(int))&__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n));
        w = 12;
        e = 14;
        *f = 32;
        m = 4;
        n = 10;
        //这里是调用了这个block
        ((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 3);
    
    }
    

    4.Block修改变量

    先上代码
    #import "DDBlockController.h"
    
    @interface DDBlockController ()
    @property (nonatomic, assign) int a;
    @property (nonatomic, copy) NSString *str;
    @end
    
    static int b = 11;
    int c = 10;
    NSString *str1 = @"aaaaaa";
    static NSString *str2 = @"bbbbbb";
    
    
    @implementation DDBlockController
    {
        int d;
        NSString *str3;
        void (^testTwo)(void);
        
    }
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.a = 123;
        self.str = @"eeeeeeeee";
        d = 4444444;
        str3 = @"rrrrrrrrrrrrrrrrr";
        [self blockTest];
        // Do any additional setup after loading the view.
    }
    
    - (void)blockTest{
        //如果修改局部非静态变量需要使用__block修饰.
        int e = 10;
        static int f = 12;
        NSString * str4= [[NSString alloc]init];;
        str4 = @"123123";
        static NSString * str5= @"1234asdads";
    
        void (^testOne)(void) = ^(void){
            
            NSLog(@"%d",_a);
            NSLog(@"%d",b);
            NSLog(@"%d",c);
            NSLog(@"%d",d);
            NSLog(@"%d",e);
            NSLog(@"%d",f);
            NSLog(@"%@",_str);
            NSLog(@"%@",str1);
            NSLog(@"%@",str3);
            NSLog(@"%@",str4);
            NSLog(@"%@",str5);
    
            
        };
        testOne();
    }
    
    - (void)dealloc{
        NSLog(@"aaaaaaaaaaaaaa");
    }
    @end
    
    //*desc和flags之间的是被捕获的内容,self(_self)将_self赋值给self,  DDBlockController *self = __cself->self; // bound by copy,从__DDBlockController__blockTest_block_impl_0中取出self的值(C++->取值符号)
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {
    
    
    
    将block中的打印依次打开,从源码中可以看出(注捕获的意思就是持有的意思)
    (1).使用当前类属性的时候self.a时,会将持有a的对象self捕获,可以修改值,但是注意循环引用(DDBlockController *_self)
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {
    
    (2).当使用全局静态变量b的时候未捕获任何值,用的时候直接使用,可以修改值
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {
    
    (3).当使用全局变量c的时候未捕获任何值,用的时候直接取,可以修改值
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {
    
    (4).当使用成员变量d的是,捕获的是对其持有的self,可以修改其值,但是注意循环引用问题(DDBlockController *_self)
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {
    
    (5).当使用局部变量e的时候会捕获其值,未捕获其指针地址,不可修改值除非使用__block修饰(int _e)
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int _e, int flags=0) : e(_e) {
    
    (6).当使用局部静态变量f的时候,捕获的是f的指针,可以进行修改(int *_f,)
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int *_f, int flags=0) : f(_f) {
    
    (7).当使用全局的属性self.str时,捕获的是持有str的对象self,可以修改值,但是注意循环引用(DDBlockController *_self)
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {
    
    (8).当使用全局变量str1的时候,未捕获任何值,因此可以直接使用且可以修改值
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {
    
    (9).当使用成员变量d的是,捕获的是对其持有的self,可以修改其值,但是注意循环引用问题(DDBlockController *_self)
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {
    
    (10).当使用局部变量str4的时候,捕获的是str4的值并没有捕获其指针地址,所以不可修改,除非使用__block修饰(NSString *_str4)
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, NSString *_str4, int flags=0) : str4(_str4) {
    
    (11).当使用局部静态变量str5的时候,捕获的是str5的地址,所以可以修改(NSString **_str5)
    __DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, NSString **_str5, int flags=0) : str5(_str5) {
    
    总结
    //auto变量(局部非静态变量)捕获的是值所以不能操作修改值.
    //注意因为当前的block是局部的并未被self持有,所以使用self不会引起循环引用
    //__block的作用是将修饰的变量包装成一个结构体对象
    下面的这个是__block修饰的int e;被block捕获的后,系统为其创建的一个结构体
    struct __Block_byref_e_0 {
        void *__isa;
      __Block_byref_e_0 *__forwarding;//__forwarding指向的是__Block_byref_e_0自己,自己指向了自己
       int __flags;
       int __size;
       int e;
      };
      
    //对于可变变量(NSMutableArray,NSMutableString等)即使是局部变量也可以进行增删改查的操作,
    //因为可变内容在初始话的时候分配了一块内存,操作的时候都是在这块内存操作的,只要是不改变该变量的指针对应的内容即可
    
    
    总结一下就是对于局部非静态变量而言,可以修改局部变量的属性等操作,但是不能改变其指向的内存的地址,否则就会报错,
    使用__block之后就是block持有了这个对象,没有使用__block的话只是copy了一份,但是只有只读权限,不能改变对象,但是可以改变对象的属性.
    
    意思就是秘书来了权限不够不好使,只能动一部分内容.老板亲自来了权限就大了可以做的事就更多了.
    

    5.Block循环引用,并不是所有的block都会循环引用
    6.Block作为形参的时候不会引起循环引用,这也就解释了为什么GCD或者UIView动画这些操作不会引起循环引用
    7.Block存储域

    相关文章

      网友评论

          本文标题:iOS OC Block详解

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