zoukankan      html  css  js  c++  java
  • Block讲解一

    Block专辑:

    本篇博客

    MRC-block与ARC-block

    Block详解一(底层分析)

    一.Block的本质

     (1)block其实是一个对象, 在存放block对象的内存区域中,也包含我们经常说的isa指针,和一些能让block正常运转的各种信息。关于isa指针,在oc中每个实例对象都会有一个isa指针,指向对象的类,其实在类里面也会有isa指针,这个指针指向该类的元类。

    (2)内存分配

    :是由编译器自动分配释放,存放函数的参数值,局部变量的值以及函数返回地址。所以我们每次调用函数,都会执行压栈操作。特点是存取效率高,存取结构连续,但是空间很小,有系统自行分配以及管理栈的地址空间。

    :由程序员分配释放,如果程序员不释放,程序结束的时候系统会收回,我们平时涉及到内存管理基本上出自于这个区域。由malloc,alloc,copy(深拷贝),new等方法触发的效果就是在堆区进行内存分配。

    静态区: 该区域其实可以细分为数据区以及BSS区。数据区存放于已经初始化好的静态变量以及全局变量,而BSS区则存放还没有初始化好的静态变量以及全局变量,由系统负责释放以及分配。

    常量区:存放常量,由系统释放以及分配。

    代码区(文本区): 存放函数体代码。

    我们定义block的时候,其所占的内存区域是分配在栈上。如果在编写程序的时候,稍加不注意,肯能会出现问题。

    void (^block)();
    if (isSure) {
      block = ^{
        NSLog(@"blockA");
      };
    }else
    {
      block = ^{
        NSLog(@"blockB");
      };
    }
    block();
    

     因为block的内存分配在栈上的,栈上的内存是系统管理的,如果编译器没有覆写待执行的block,程序正常,如果覆写了,程序就会崩溃。

    如何解决问题:也就是我们经常看到的,修饰block的时候,用copy。让block从栈中复制到堆上,copy之后该block就成了带引用计数的对象。

    void (^block)();
    if (isSure) {
      block = [^{
        NSLog(@"blockA");
      } copy ];
    }else
    {
      block = [^{
        NSLog(@"blockB");
      } copy];
    }
    block();
    

     这样写,代码就会安全。如果手动管理内存,用完之后可以手动将其释放。

    block除了存储栈和堆上,还有一个全局的block(不会捕获变量,存放在全局的内存里面),block的内存存储区域。

    block 在内存中的位置

    根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。

    NSGlobalBlock:类似函数,位于text段;当我们声明一个block时,如果没有这个block没有捕获外部的变量,那么这个block就位于全局区,此时对NSGlobalBlock的retain、copy、release操作都无效,ARC与MRC均是如此。


    NSStackBlock:位于栈内存,函数返回后Block将无效;平时编程的时候很少遇到位于栈区的block,为什么呢?因为在ARC环境下,当我们声明并且定义了一个block,并且没有为Block添加额外的修饰符(默认是__strong修饰符),如果该Block捕获了外部的变量,实质上是有一个从__NSStackBlock__转变到__NSMallocBlock__的过程,只不过是系统帮我们完成了copy操作,将栈区的block迁移到堆区,延长了Block的生命周期。对于栈区block而言,栈block在当函数退出的时候,该空间就会被回收。

    那什么时候在ARC的环境下出现__NSStackBlock__呢?如果我们在声明一个block的时候,使用了__weak或者__unsafe__unretained的修饰符,那么系统就不会为我们做copy的操作,不会将其迁移到堆区。


    NSMallocBlock:位于堆内存。们需要手动调用copy方法才可以将block迁移到堆区,而在ARC环境下,__strong修饰的(默认)block只要捕获了外部变量就会位于堆区,NSMallocBlock支持retain、release,会对其引用计数+1或 -1。

     1 示例1:  
     2   
     3 BlkSum blk1 = ^ long (int a, int b) {  
     4   return a + b;  
     5 };  
     6 NSLog(@"blk1 = %@", blk1);// 打印结果:blk1 = <__NSGlobalBlock__: 0x47d0>  
     7   
     8   
     9   
    10 示例2:  
    11   
    12 int base = 100;  
    13 BlkSum blk2 = ^ long (int a, int b) {  
    14   return base + a + b;  
    15 };  
    16 NSLog(@"blk2 = %@", blk2); // 打印结果:blk2 = <__NSStackBlock__: 0xbfffddf8>  
    17   
    18   
    19 示例3:  
    20   
    21 BlkSum blk3 = [[blk2 copy] autorelease];  
    22 NSLog(@"blk3 = %@", blk3); // 打印结果:blk3 = <__NSMallocBlock__: 0x902fda0> 

     blk1和blk2的区别在于:

    blk1没有使用block以外的任何外部变量,Block不需要建立局部变量值的快照,这使得block1与一般函数没有任何区别;而blk2使用了局部变量base。

    注意:在定义blk3时,局部变量base被copy到栈上,作为常量供block使用,列如下面的结果为103,而不是104

    1 int base = 50;  
    2   base += 50;  
    3   BlkSum sum = ^ long (int a, int b) {  
    4     return base + a + b;  
    5   };  
    6   base++;  
    7   printf("%ld",sum(1,2));  

    在Block内变量base是只读的,如果想在block内改变base的值,在定义base时要用__block修饰

    __block int base = 50;

    __block int base = 50;  
    base += 50;  
    BlkSum sum = ^ long (int a, int b) {  
      base += 10;  
      return base + a + b;  
    };  
    base++;  
    printf("%ld
    ",sum(1,2));  
    printf("%d
    ",base); 

    输出将是114,111;

    注意:Block中使用__block修饰的变量时,将取变量此刻运行的值,而不是定义的快照。

    如果将block捕获的外部变量使用static修饰或者将外部变量声明为全局变量,那么block是可以直接修改该外部变量的,因为全局变量或静态变量在内存中的地址是固定的(存放于上文中的静态区),Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量。

    拓展:(3)中也有讲到:

    局部自动变量:在block中只读。block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在block外改变,也不影响在block中的值。

    举例如下:

    int base = 100;  
    BlkSum sum = ^ long (int a, int b) {  
      // base++; 编译错误,只读  
      return base + a + b;  
    };  
    base = 0;  
    printf("%ld
    ",sum(1,2)); // 这里输出是103,而不是3  

    static 修饰变量,效果与__block一样

    static int base = 100;  
    BlkSum sum = ^ long (int a, int b) {  
      base++;  
      return base + a + b;  
    };  
    base = 0;  
    printf("%d
    ", base);  
    printf("%ld
    ",sum(1,2)); // 这里输出是3,而不是103 
    printf("%d ", base);

    输出结果时:0;4;1

    表明Block外部对base的更新会影响Block中的base的取值,同样Block对base的更新也会影响Block外部的base值。

    得出一个结论:Block变量,被__block修饰的变量称作Block变量。基本类型的Block变量等效于全局变量、或静态变量

    (3)捕获变量

    下面有一个问题,说明:

    int  c = 6;
        
        int (^addBlock) (int a) = ^(int a){
          
            return a + c;
        };
        
        int addValue = addBlock(2);// addValue = 8;
    }
    

     在声明的block范围内,所以变量都可以为其捕获,也就是说,在那个范围内所有的变量,在块里面都可以使用。如果要想在外面改变变量值的话,就必须使用__block修饰。

    __block int add = 5;
    

     分析:

    栈里面的block:
    如果该block储存在栈里面,那么该block只会在声明的作用范围内有效,作用域结束的时候,栈上的__block变量和block也会被废弃。也就是说block和捕获的变量被系统一块释放了。在栈里面的__block变量只是被block使用而已,而没有被block所持有。

    堆里面的block:
    当栈里面的block被Copy到堆里面的时候,__block变量也会被copy到堆里面并且会被block所持有,只有不被block持有的时候才会被释放。

    全局里面block:只有不被block持有的时候才会被释放。

    (4)block封装了一段代码,可以在任何时候执行;block可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或者返回值;它和传统函数指针类似,但是有区别:block是inline(内联函数)的,并且默认情况下,blokc对局部变量都是只读的;block代码:是一个函数对象,是在程序运行过程中产生的,普通函数:是一段固定代码,产生于编译期。

    二.Block的定义与使用

    (1)完整定义如下:

     

    returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

    举例说一下:

    void (^myBlock)(void);//无返回值,无参数
    void(^myBlock)(int,int);//无返回值,有参数
    NSString *(^myBlock)(NSString *name ,int age);//有返回值和参数,并且在参数类型后面加入了参数名(仅为可读性)
    

     (2)block的变量的声明

    block变量的声明格式如下:返回值类型(^block名字)(参数列表);----形参变量名称可以省略,只保留变量类型即可

    // 声明一个无返回值,参数为两个字符串对象,叫做aBlock的Block  
    void(^aBlock)(NSString *x,NSString * y);
      
    // 形参变量名称可以省略,只留有变量类型即可  
    void(^aBlock)(NSString *, NSString *);
    

     (3)block的定义和举例

    /*定义属性,block属性可以用Strong修饰,也可以用copy修饰**/
    @property (nonatomic,strong)void(^myBlock)();//无参无返回值
    @property (nonatomic ,strong)void(^myBlock)(NSString *);//带参数无返回值
     @property (nonatomic, strong) NSString *(^myBlock2)(NSString *);  //带参数与返回值  
    
    //定义变量
    void (^myBlock)() = nil;//无参无返回值
    void (^myBlcok)(NSString *) = nil;//带参数无返回值
    NSString *(^myBlock)(NSString *) = nil;//带参数无返回值
    
    block被当做方法的参数
    格式:(block类型)参数名称
    - (void)test:(void(^)())testBlock;//无参无返回值
    - (void)test:(void(^)(NSString *))testBlock; //带参数无返回值 
    - (void)test:(NSString *(^)(NSString *))testBlock;//带参数与返回值  
    
    
    使用typedef定义block
    typedef  void (^myBlock)();//以后就可以使用myBlock定义无参无返回值的
    typedef void (^myBlock)(NSString *) //使用myBlock1定义参数类型为NSString的block  
    typedef (NSString *)(^myBlock)(NSString *);//使用myBlock2定义参数类型为NSString,返回值也为NSString的block  
    //定义属性
    @property (nonatomic,strong)myBlock  testBlock;
    //定义变量
    myBlock testBlock = nil;
    //当做参数
    - (void)test:(myBlock)testBlock;
    

     (3)block的赋值

    格式:block =  ^返回值类型(参数列表){}

    注4: 通常情况下,可以省略返回值类型,因为编译器可以从存储代码块的变量中确定返回值的类型。

    没有参数没有返回值  
    myBlock testBlock = ^void(){  
         NSLog(@"test");  
     };  
      
    没有返回值,void可以省略  
    myBlock testBlock1 = ^(){  
         NSLog(@"test1");  
     };  
      
    没有参数,小括号也可以省略  
    myBlock testBlock2 = ^{  
         NSLog(@"test2");  
     };  
      
    有参数没有返回值  
    myBlock1 testBlock = ^void(NSString *str) {  
          NSLog(str);  
    }  
      
    省略void  
    myBlock1 testBlock = ^(NSString *str) {  
          NSLog(str);  
    }  
      
    有参数有返回值  
    myBlock2 testBlock = ^NSString *(NSString *str) {  
         NSLog(str)  
         return @"hi";  
    }  
      
    有返回值时也可以省略返回值类型  
     myBlock2 testBlock2 = ^(NSString *str) {  
         NSLog(str)  
         return @"hi";  
    }  

    声明Block变量的同时进行赋值

    1 int(^myBlock)(int) = ^(int num){  
    2     return num * 7;  
    3 };  
    4   
    5 // 如果没有参数列表,在赋值时参数列表可以省略  
    6 void(^aVoidBlock)() = ^{  
    7     NSLog(@"I am a aVoidBlock");  
    8 }; 

    Block变量的调用

    1 // 调用后控制台输出"Li Lei love Han Meimei"  
    2 aBlock(@"Li Lei",@"Han Meimei");  
    3   
    4 // 调用后控制台输出"result = 63"  
    5 NSLog(@"result = %d", myBlock(9));  
    6   
    7 // 调用后控制台输出"I am a aVoidBlock"  
    8 aVoidBlock(); 

    Block作为OC函数参数

     1 //1.定义一个形参为Block的oc函数
     2 - (void)userBlockForOC :(int (^)(int ,int ))aBlock{
     3     NSLog(@"result = %d", aBlock(300,200));  
     4 }    
     5 
     6 //2.声明并赋值定义一个Block变量
     7 int (^addBlock)(int ,int )= ^(int x,int y){
     8   return x+ y;  
     9 }
    10 
    11 //3.以Block作为函数参数,把参数像对象一样传递
    12 [self userBlockForOC:addBlock];
    13 
    14 //4. 将第2点和第3点合并一起,以内联定义的Block作为函数参数  
    15 [self useBlockForOC:^(int x, int y){  
    16     return x+y;  
    17 }]; 

     (4)block在定义时并不会执行内部的代码,只有在调用时候才会执行。下面通过两个例子:

     1 //在AViewController.h定义
     2 @property (nonatomic,copy)void (^successBlock)(NSInteger count);
     3 
     4 //在在MyViewController.m赋值
     5 if(self.successBlock && ){
     6   self.successBlock([self.cards count]);  
     7 }
     8 
     9 //在BViewController.m中调用:  
    10 AViewController *aa = [[AViewController alloc]init];
    11 //回调要如何处理
    12 aa.successBlock  = ^(NSInteger count){
    13  if(count == 0){
    14   //处理代码  
    15  }
    16 }

    再看一个例子

     1 //1.在IOABgPopView.h中
     2 
     3 #import <UIKit/UIKit.h>
     4 
     5 typedef void (^popCallback)();
     6 
     7 @interface IOABgPopView : UIView
     8 
     9 + (IOABgPopView *)show;
    10 
    11 @property(nonatomic,copy)popCallback clickCallBack;
    12 
    13 @end
    14 
    15 
    16 //2.在IOABgPopView.m中
    17 
    18 - (void)closeTap{
    19     if (self.clickCallBack) {
    20         self.clickCallBack();
    21     }
    22 }
    23 
    24 //3.调用
    25 {
    26      IOABgPopView *show = [IOABgPopView show];
    27     self.show = show;
    28     WS(weakSelf);
    29     self.show.clickCallBack = ^{
    30         [weakSelf closeTap];
    31    };
    32 }
    33    

    最后一个例子:用typedef为block进行重命名

    我们可以使用typedef为block进行一次重命名,方法跟为函数指针命名是一样的:
     1 typedef int (^sum)(int ,int);

    这样我们就利用typedef定义了一个block,这个block的名字就是宿命,需要传两个参数,应该这样使用

    Sum mysum = ^(int a,int b){
      n = 2;
      return (a + b) *n;
    }

    使用如下

     1 typedef int (^Sum) (int, int);  
     2 
     3 int main(int argc,const charchar *argv){
     4  __block int n = 1;
     5  @autoreleasepool {
     6    Sum mysum = ^(int a,int b ){
     7       n = 2;
     8       return (a + b)*n;
     9     };
    10     NSLog(@"(3 + 5) * %i = %d", n, mysum(3, 5)); 
    11  }
    12  return 0;
    13 }    

     三 提到block,无论是面试还是实际开发中,都会提到一个词语“循环引用”,首先看下面一个例子:

    (1)

     1 @implementation TsetBlock  
    3 -(id)init{
    5 if (self = [superinit]) { 6 self.testStr =@"中国"; 7 self.block = ^(NSString *name, NSString *str){ 8 NSLog(@"arr:%@",self.testStr); // 编译警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle 9 }; 10 } 11 returnself; 12 } 13 @end

    看到这个例子,很多人都表述为“block里面使用self导致循环引用”其实这种说法是不严谨的,不一定出现“self”字眼才会引起循环引用,再比如:

     1 @implementation TsetBlock  
     2   
     3 -(id)init{  
     4   
     5    if (self = [superinit]) {  
     6        self.testStr =@"中国";  
     7         self.block = ^(NSString *name,NSString *str){  
     8            NSLog(@"arr:%@", _testStr); // 同样出现: Capturing 'self' strongly in this block is likely to lead to a retain cycle  
     9        };  
    10    }  
    11    returnself;  
    12 }  
    13 @end 

    可以发现block代码中没有显示self,也会出现循环引用!所以只要你在block里面用到了self所拥有的的东西!

    解决方案:

    在ARC下不用__block,而是用__weak为了避免出现循环引用。

    1.ARC:用__week

    __weaktypeof (self)  weakSelf = self; 或者

    __weak someClass *weakSelf = self;

    2.MRC:用__block ,__block修饰的变量在Block copy时是不会retain的,所以,也可以做到破解循环引用。
    __block someClass *blockSelf = self;

    Block的copy、retain、release操作

    对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;

    NSGlobalBlock:retain、copy、release操作都无效;
    NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。
    NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
    尽量不要对Block使用retain操作。

    (2)block 循环引用???

    因为两个对象相互持有,这样就会造成循环引用。如下图所示:

    两个对象相互持有,对象A持有对象B,对象B持有对象A,相互持有,最终导致两个对象都不能释放。

    1.block在主函数内体用到了self/self.变量/[self 方法]意味着block对self进行持有操作;

    2.self声明了属性变量block,block用copy修饰,意味着:self对block进行持有操作,会造成循环引用。

     1 typedef void(^block)();  
     2   
     3 @property (copy, nonatomic) block myBlock;  // 2  
     4 @property (copy, nonatomic) NSString *blockString;  
     5   
     6   
     7 - (void)testBlock {  
     8     self.myBlock = ^() {  
     9         //其实注释中的代码,同样会造成循环引用  
    10         NSString *localString = self.blockString; // 1  
    11           //NSString *localString = _blockString;  
    12           //[self doSomething];  
    13     };  
    14 }  

    解决方案:

    1 解决方法:  
    2   
    3 __weak typeof(self) weakSelf = self;  
    4 self.myBlock = ^() {  
    5     NSString *localString = weakSelf.blockString;  
    6 }; 

    什么时候在block中又不需要weakSelf???不会造成循环引用???

    1.大部分的GCD方法

     1 dispatch_async(dispatch_get_main_queue(), ^{ 2 [self doSomething]; 3 }); 

    因为self并没有对GCD的block进行持有,没有造成循环引用,

    2.大部分的动画效果

    当动画结束时,UIView 会结束持有这个 block,block 对象就会释放掉,从而 block 会释放掉对于 self 的持有。整个内存引用关系被解除

    3.block并不是对象的属性/变量,而是方法的参数/临时变量

     1 -  (void) doSomething {  
     2     [self testWithBlock:^{  
     3         [self test];  
     4     }];  
     5 }  
     6   
     7 -  (void) testWithBlock:(void(^)())block {  
     8     block();  
     9 }  
    10   
    11 -  (void) test {  
    12     NSLog(@"test");  
    13 }

    这里因为block只是一个临时变量,self并没有对其持有,所以没有造成循环引用

     说到了weakSelf,还有一个strongSelf???

    看下面一种情况:

    1 __weak __typeof__(self) weakSelf = self;    
    2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
    3   
    4 [weakSelf doSomething];  
    5 [weakSelf doOtherThing];  
    6   
    7 });  

    在 doSomething 中,weakSelf 不会变成 nil,不过在 doSomething 执行完成,调用第二个方法 doOtherThing 的时候,weakSelf 有可能被释放,于是,strongSelf 就派上用场了:

    1 __weak __typeof__(self) weakSelf = self;  
    2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
    3   
    4 __strong __typeof(self) strongSelf = weakSelf;  
    5 [strongSelf doSomething];  
    6 [strongSelf doOtherThing];  
    7   
    8 });  

    __strong确保在block内,strongSelf不会被释放

    总结如下:

    1.在Block内如果访问self的方法、变量、建议使用weakSef

    2.在Block中需要多次访问self,则需要使用StrongSelf

    最后说一下:

    iOS为什么要用copy修饰:

    默认情况下,block是存在栈中的,可能被随时回收,通过copy操作将其在堆中保留一份,相当于一直被强引用着,因此如果block用到self,需要将其弱化, 通过__weak或者__unsafe_unretained.  

    如果属性的block使用assign修饰时,当再次访问时就会出现野指针访问。

  • 相关阅读:
    iis 配置域名访问
    js将base64做UrlEncode转码
    vue-router 刷新当前路由
    iview default-file-list 动态赋值不显示
    vue2.0 axios 登录post请求自动读取Set-Cookie设置
    iis 使用主机名配置需注意
    【LeetCode.1】 求两数之和
    【Docker学习笔记】Docker常用命令学习
    【Docker学习笔记】Docker基本组成与安装
    微信小程序对接七牛云 上传多张图片、预览、删除 (测试可用)
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/8203616.html
Copyright © 2011-2022 走看看