美文网首页
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