美文网首页
iOS与OS X多线程和内存管理(二)Blocks

iOS与OS X多线程和内存管理(二)Blocks

作者: 丁宝2012 | 来源:发表于2018-08-22 10:48 被阅读28次

    前言

    上篇文章整理了关于“内存管理”的知识点,这篇主要整理“Blocks”的内容,在学习Blocks的内容时,我一开始也采取了学习“内存管理”时采用的先阅读《iOS与OS X多线程和内存管理》再配合《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法 》,但是在学习《iOS与OS X多线程和内存管理》时有些理解比较困难,恰恰《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法 》中的内容虽然比较少,但对阅读《iOS与OS X多线程和内存管理》中的内容很有帮助,当然还有别的学习途径。以下内容如有理解错误的地方,希望大家指出,我们一起学习和探讨~

    什么是blocks

    BIocks是C语言的扩充功能,可概括为:带有自变量值的(局部变量)的匿名函数(匿名函数就是不带名称的函数)

    • C语言的函数中可能使用的变量

      • 自动变量(局部变量)
      • 函数的参数
      • 静态变量(静态局部变量)
      • 静态全局变量
      • 全局变量
    • 在函数的多次调用之间能够传递值的变量(虽然这些变量的作用域不同,但在整个程序中,一个变量总保持在一个内存区域,所以虽然多次调用函数,该变量值总保持不变)

      • 静态变量(静态局部变量)
      • 静态全局变量
      • 全局变量
    • C++和OC使用类可保持变量值且能够多次持有该变量自身,它会声明持有成员变量的类,有类生成的实例或对象保持该成员变量的值

    • ‘带有自变量值的的匿名函数’这一个概念不仅指Blocks,它还存在于其他许多程序语言中,在计算机科学中,次概念也称为‘闭包(closure)’、‘lambda’


      FD8CBA1E-3A58-48D1-8DE9-1BDD7061F00D.png

    Blocks模式

    Block语法(Block表达式语法)

    • Block语法与函数的不同

      • 没有函数名
      • 带有 ‘^’
    • Block语法(无省略)

    3B1C4EF3D014B48AC387BB30F4EF3B19.png
       ^int (int count){return count + 1;}
    //表达式中含有return语句时,其类型必须与返回值类型相同
    
    • Block语法(省略返回值类型)
    EBD14914984DE1B285DDDC1D77552435.png
       ^ (int count){return count + 1;}
    //省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果表达式中没有return语句就是void类型,表达式中含有多个return语句时,所以return的返回值类型必须相同
    
    • Block语法(省略返回值类型、参数列表)
    71CC51D2A6B5806002181794275456AE.png
    //不使用参数的Block语法
     ^void (void){ }
    //省略后表达式为
     ^{ }
    
    

    Block类型变量

    • 在Block语法下,可将Block语法赋值给声明为Block类型的变量中,即代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的‘值’

    • Blocks中由Block语法生成的值也被称为‘Block’

    • Block即指代码中的Block语法,又指由Block语法所生成的值

    • 声明Block类型变量

      int (^blk)(int);
    返回类型(^变量名)(参数)
    
    • Block类型的变量用途
      • 自动变量
      • 函数参数
      • 静态变量
      • 静态全局变量
      • 全局变量
      //-使用Block语法将Block赋值为Block类型变量
      int(^blk)(int) = ^int(int count){return count + 1;};
    
      //由Block类型变量向Block类型变量赋值
      int(^blk1)(int) = blk;
      
      int(^blk2)(int);
      blk2 = blk1;
    
      //在函数参数中使用Block类型变量可以函数传递Block(值)
      void fun(int(^blk)(int)){
        
      }
    
      //在函数返回值中指定Block类型,可以将Block作为函数的返回值返回
       int(^func())int{
            return ^(int count){return count + 1;};
        }
    
      //使用  typedef 
      typedef int(^blk_t) int;
      //原记述方式
      void fun(int(^blk)(int)){
      }
      //等价于
      void fun(int(blk_t blk){
      }
    
      //原记述方式
        int(^func())int{
            return ^(int count){return count + 1;};
        }
      //等价于
      blk_t func{
      }
    
    

    截获自动变量值

    • ‘带有自动变量值’在Blocks中表现为‘截获自动变量值’
      int main{
        int day = 256;
        int val = 10;
        const char *fmt = "val = %d\n";
        void (^ blk)(void) = ^{
            printf(fit , val);
         };
        
        val = 2;
        fmt = "There values were changed . val = %d\n";
        blk();
        return 0;
      }
    
      /*
        *Block语法的表达式使用的是它之前声明的自动变量fmt和val
        *Blokcs中,Block表达式中截获所使用的自动变量的值,即Block表达式保存该自动变量的瞬间值
        *执行Block语法后,即使改写Block中使用的自动变量也不会影响Block执行时自定变量的值
        *结果输出 val = 10;
        */
    

    _ _block说明符

    • 自动变量的值截获只能保存执行Block语法瞬间的值,保存后就不能改写该值
      int val = 0;
      void (^blk)(void) = ^{ val = 1;};
      blk();
      printf("val = %d\n");
    
      //编译报错
    
    • 如果想在Block语法表达式中将值赋给Block语法外声明的自动变量(例如:val),需要在该自动变量上附加_ _block说明符
      _ _block int val = 0;
      void (^blk)(void) = ^{ val = 1;};
      blk();
      printf("val = %d\n");
    
      //结果输出:val = 1;
    
    • 使用附有_ block说明符在自动变量可以在Block中赋值,该变量称为 _block变量

    截获自动变量

       id array = [[NSMutableArray alloc] init];
       void (^blk) (void) = ^{
            id obj = [[NSObject alloc] init];
            [arrray addObject:obj];
        }
        /*
         *编译不会报错
         *代码中截获的是变量值为NSMutableArray类的对象(即截获的是NSMutableArray类对象用的结构体实例指针)
         *截获OC对象,调用变更改对象的方法(addObject),即使用截获对象不会出现任何错误
         */
    
       id array = [[NSMutableArray alloc] init];
       void (^blk) (void) = ^{
            array = [[NSMutableArray alloc] init];
        }
        /*
         *编译报错
         *向截获的变量array赋值会产生编译错误
         */
    
        _ _block id array = [[NSMutableArray alloc] init];
       void (^blk) (void) = ^{
            array = [[NSMutableArray alloc] init];
        }
        //编译成功
    
    

    Blocks实现

    Block的实质

    • Block即为Object_C的对象
    • C语言中有一下存储类的说明符
      • typedef
      • extern
      • static(作为静态变量存储在数据区)
      • auto(表示作为自动变量存储在栈中)
      • register

    Block存储域

    • Block转换为Block的结构体类型自动变量
    • _ block变量转换为 _block变量的结构体类型自动变量
    • 结构体类型的自动变量,即栈上生成的该结构体的实例
    1E0C18A7-FB73-4E72-95D8-5C30B2AD01DA.png
    • Block也是OC对象,该Block的类


      FDA8982E-C03A-46CE-8321-AE8D4E28DC85.png
    C9F96C19-10DF-4D5B-99BC-EA3B465B377D.png
    • 记述全局变量的地方有Block语法时、Bolck语法的表达式中不使用应截获的自动变量时,Block为_NSConcreteGlobalBlock类的对象,即Block配置的程序的数据区域中
    • 除上述情况Block配置的程序的栈上
      -配置在全局变量上的Block,从变量的作用域外也可以通过指针安全使用
    • 配置在栈上的Block,如果其所属的变量作用域结束,该Block就被废弃,由于_ _block变量也配置在栈上,同理。
    2C9642D6-9F9B-439F-8D37-D88C197E1787.png
    • Blocks提供了将Block和_ _block变量从栈上复制到堆上的方法来解决,将配置在栈上的Block复制到堆上,这样即使Block语法记述的作用域结束,堆上的Block还是可以继续存在(ARC有效,自动生成将Block从栈上复制到堆上)
    01674628-B6AD-4843-888A-23DDC7D2E66B.png

    _ _block变量存储域

    • Block从栈中复制到堆上时,_ _block变量也会受影响(下图)
    image
    • 若在1个Block中使用_ block变量,当该Block从栈中复制到堆上,使用的所有 block变量也必定配置在栈上,这些 block变量也全部被从栈中复制到堆上,此时,Block持有堆 _block变量,即使在该Block已被复制在堆上的情况下,复制Block也对所使用的_block变量没有任何影响(下图)
      3FD99E0A-F6A2-4C4B-AEC0-98089992EEBC.png
    • 在多个Block中使用_ block变量时,因为最先会将所有的Bolck配置在栈上,所以 block变量也会配置在栈上,在任何一个Block从栈复制到堆时, block变量也会一并从栈中复制到堆并被该Block所持有,当剩下的Block从栈中复制到堆时,被复制的Block持有 block变量(之前复制到堆上的),并增加 _block变量的引用计数(下图)

      65B9DA56-8EC4-41CB-BE34-0884FC1F45E3.png
    • 如果配置在堆上的Block被废弃,那么它所使用的_ block变量也就被释放(同理引用计数式内存管理,使用 block变量的Block持有 block变量,如果Block被废弃,它所持有的 _block变量也就被释放)(下图)

      723DF0DA-B19B-4D66-8A64-1FD9129DEDD8.png
    • Block的_ _main_block_impl_0结构体实例持有指向 _ _block变量的 _ _Block_dyref_val_0结构体实例指针
    • _ _Block_dyref_val_0结构体实例的成员变量 _ _forwarding持有指向该实例自身的指针,通过成员变量 _ _forwarding访问成员变量val(成员变量val是该实例自身持有的变量,它相当于原自动变量)
    66FC9034-763C-412F-AE51-070DFA21666E.png
    • 不管_ block变量配置在栈上还是堆上,都能够正确的访问该变量(通过Block的复制,_ block变量也从栈上复制到堆,此时可同时访问栈上的 block变量和堆上的 _block变量)
      • 栈上的_ block变量用结构体实例在 _block变量从栈复制到堆上时,会将成员变量 _ forwarding的值替换为复制目标堆上 _block变量用结构体实例的地址
      • 通过该功能,无论是在Block语法中,Block语法外使用,_ block变量,还是 block变量配置在栈上还是堆上,都可以顺利访问同一个 _block变量。
    BF70ABC0-60DC-4C31-9550-41EB64317008.png

    截获对象

    • 生成并持有NSMutableArray类的对象,由于附有_strong修饰符的赋值目标变量作用域立即结束,所以对象也被立即释放并废弃
        id array = [[NSMutableArray alloc] init];
    
    • 在Block语法中使用该变量array
        blk_t blk;
        {
            id array = [[NSMutableArray alloc] init];
            blk = [^(id obj){
                [array addObjct:obj];
                NSLog(@"array count = %ld",[array coun]);
          } copy];
        }
        blk ([[NSObject alloc] init]);
        blk ([[NSObject alloc] init]);
        blk ([[NSObject alloc] init]);
    
        //输出结果
        array count = 1
        array count = 2
        array count = 3
    
       /*
        *输出结果意味着,赋值给变量array的NSMutableArray类的对象在该源代码最后Block的执行部分超出变量作用域而存在
        *被赋值NSMutableArray类对象并被截获的自动变量array,它是Block用的结构体中附有_strong修饰符的成员变量
        struct _main_block_impl_0{
          struct _block_impl impl;
          struct _main_block_desc_0 *Desc;
          id strong array;
        }
        *Block用结构体中,还有附有_strong修饰符的对象类型变量array,所以需要恰当管理赋值给变量array的对象,因此_main_block_copy_0函数使用_Block_object_assjgn函数将对象类型对象赋值给Block用结构体的成员变量array中并持有该对象
        *_Block_object_assjgn函数调用相当一retain实例方法函数,将对象赋值给对象类型的结构体成员变量中
        *_main_block_dispose_0函数使用_Block_object_dispose函数,释放赋值在Block用结构体成员变量array中的对象,相当于release实例方法,释放赋值在对象类型结构体成员变量中的对象
        */
    
    • 在Block从栈复制到堆时以及堆上的Block被废弃时会调用的函数
    4203D13C-5B6B-4073-8AD2-942AB2D2A890.png
    • 什么时候栈上的Block会复制到堆?

      • 调用Blcok的coyp实例方法时(如果Block配置在栈上,那么该Block会从栈上复制到堆)
      • Block作为函数返回值返回时(编译器自动的将对象的Block作为参数并调用_Block_cpoy函数-等同与copy实例方法)
      • 将Block赋值给附有_strong修饰符id类型的类或Block类型成员变量时(编译器自动的将对象的Block作为参数并调用_Block_cpoy函数-等同与copy实例方法)
      • 在方法名中含有usingBlock的Cocoa框架方法或GrandCentralDispatch的API中传递Block时(该方法或函数内部对传递过来的Block调用Block的copy实例方法或_Block_cpoy函数)
    • 上述情况下栈上的Block被复制到堆上,其实实质是_Block_cpoy函数被调用时Block从栈上复制到堆
      -在释放复制到堆上的Block后,谁都不持有Blcok而是其被废弃时调用despose函数,相当于对象的release实例方法

    • 综上所述,通过使用附有_strong修饰符的自动变量,Block中截获的对象就能够超出其变量的作用域而存活

    • 截获对象时和使用_ _block对象时copy函数或dispose函数的不同

    4888C201-5B2E-4BB5-A492-8463000A0CA7.png
    • 通过BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF参数,区分copy函数和dispose函数的对象类型时对象还是_ _block变量
    • 与copy函数持有截获的对象,dispose函数释放截获的对象相同,copy函数持有所使用的_ block变量,dispose函数释放所使用的 _block变量
    • Block中使用的赋值给附有strong修饰符的自动变量的对象和复制到堆上的 _block变量由于被堆上的Block所持有,因而可超出其作用域而存在
    • 只有调用_Block_cpoy函数才能持有截获的附有_strong修饰符的对象类型的自动变量值,如果没有调用_Block_cpoy函数,即使截获了对象,它也会随着变量的作用域结束而被废弃
    • Block 中使用对象类型自动变量时,除以下情况外,推荐调用Block的copy实例方法
      • Blcok作为函数返回值返回时
      • 将Block赋值给类的附有_strong修饰符的id类型或Block类型成员变量时
      • 在方法名中含有usingBlock的Cocoa框架方法或GrandCentralDispatch的API中传递Block时

    _ _block变量和对象

    • _ _block说明符可指定任何类型的自动变量

    • 在Block赋值给附有_strong修饰符的id类型或对象类型自动变量的情况下,当Block从栈中复制到堆时,使用_Block_object_assjgn函数,持有Block截获的对象,到堆上的Block被废弃时,使用_Block_object_dispose函数函数,释放Block截获的对象

    • 在_ _block变量为附有strong修饰符的id类型或对象类型自动变量的情况下,当 _block变量从栈中复制到堆时,使用Block_object_assjgn函数,持有赋值给 block变量的对象,到堆上的 _block变量被废弃时,使用Block_object_dispose函数函数,释放赋值给 _block变量的对象

    • 即使对象赋值复制到堆上的附有strong修饰符的对象类型 block变量中,只有 _block变量在堆上继续存在,那么该对象就会继续处于被持有的状态

    • 在Block语法中使用该变量array

        blk_t blk;
        {
            id array = [[NSMutableArray alloc] init];
            id _ _weak array2 = array;
            _ _block id _ _weak array3 = array;
            blk = [^(id obj){
                [array2 addObjct:obj];
                NSLog(@"array2 count = %ld,array2 count = %ld",[array2 coun],[array3 coun]);
          } copy];
        }
        blk ([[NSObject alloc] init]);
        blk ([[NSObject alloc] init]);
        blk ([[NSObject alloc] init]);
    
        //输出结果
        array2 count = 0 array3 count = 0
        array2 count = 0 array3 count = 0
        array2 count = 0 array3 count = 0
    
    
       /*
        *由于附有_strong修饰符的变量array在该变量作用域结束的同时释放、废弃,nil被赋值在附有_ _weak修饰符的变量array2中,即使附加 _ _block修饰符附有_strong修饰符的变量array在该变量作用域结束的同时释放、废弃,nil被赋值在附有_ _weak修饰符的变量array3中
        */
    

    Block循环引用

    • 通过_weak修饰符,避免循环引用
    typedef void (^blk_t) (void);
    
    @interface MyObject : NSObject
    {
        blk_t blk_;
    }
    @end
    
    
    @implementation MyObject
    
    -(id)init{
        self = [super init];
        blk_ = ^{ NSLog(@"self = %@", self); }
        return self;
    }
    
    -(void)dealloc{
        NSLog(@"dealloc");
    }
    
    @end
    
    int mian(){
        id o = [[MyObject alloc] init];
        NSLog(@"%@", o );
        return o;
    }
    
    /*
     * MyObject类的dealloc实例方法一定没有被调用
     * MyObject类对象的Block类型成员变量blk_持有赋值为Block的强引用(MyObject类对象持有Block)
     *init实例方法中执行的Block语法使用附有_strong修饰符的id类型变量self
     *由于Block语法赋值在成员变量blk_中,通过Block语法生成在栈上的Block此时有栈复制在堆上,并持有所使用的self,self持有Block,Block持有self,形成循环引用
     */
    
    D7124496FF78DECEAC96909FD5C49DCF.jpg
    • 为避免此循环,可声明附有_weak修饰符的变量,并将self赋值使用
    -(id)init{
        self = [super init];
        id _ _weak tmp = self;
        blk_ = ^{ NSLog(@"self = %@", tmp); }
        return self;
    }
    
    F2598D7757C5843EE46FC8CFA534C296.jpg
    • 由于Block存在时,持有该Block的Myobject类对象即复制在变量tmp中的self必定存在,因此不需要判断变量tmp的值是否为nil

    • 使用_ _block变量来避免循环引用

    typedef void (^blk_t) (void);
    
    @interface MyObject : NSObject
    {
        blk_t blk_;
    }
    @end
    
    
    @implementation MyObject
    
    -(id)init{
        self = [super init];
        _ _block id tmp = self;
        blk_ = ^{
             NSLog(@"self = %@", tmp);
             tmp = nil;
        }
        return self;
    }
    
    -(void)execBlock{
      blk_();
    }
    -(void)dealloc{
        NSLog(@"dealloc");
    }
    
    @end
    
    int mian(){
        id o = [[MyObject alloc] init];
        [o execBlock];
        return o;
    }
    
    
    • 不执行execBlock实例方法,即不执行赋值给成员变量blk_的Block,会引起循环引用引起的内存泄露,存在循环引用:
      • MyObject类对象持有Block
      • Block持有 _ _block 变量
      • _ _block变量持有MyObject类对象
    2A6F5F4B537D7C2B4272E34F0E73A605.jpg
    • 执行execBlock实例方法,Block被实行,nil被赋值在 _ _block 变量tmp中
        blk_ = ^{
             NSLog(@"self = %@", tmp);
             tmp = nil;
        }
     /*
      *_ _block 变量tmp对MyObject类对象的强引用失效,避免循环引用
      * MyObject类对象持有Block
      * Block持有 _ _block 变量
      */ 
    
    
    AF9D11E4CFFC05E678884E0135F637DC.jpg

    copy/release

    • ARC无效时,一般需要手动使用copy实例方法将Block从栈上复制到堆。通过release实例方法释放复制的Block
    • 只要Block有一次复制并配置在堆上,就可通过retain实例方法持有(但对于配置在栈上的Block调用retain实例方法则不起作用)
    • 在ARC无效时,_ block说明符被用来避免循环引用,这是由于当Block从栈复制到堆时,若Block使用的变量为附有 block说明符的id类型或对象类型的自动变量,不会被retain,若Block使用的变量为没有 block说明符的id类型或对象类型的自动变量,则会被retain(ARC有效或无效时 _block说明符的用途不同,要注意)

    相关文章

      网友评论

          本文标题:iOS与OS X多线程和内存管理(二)Blocks

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