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修饰时,当再次访问时就会出现野指针访问。

  • 相关阅读:
    LeetCode Count of Range Sum
    LeetCode 158. Read N Characters Given Read4 II
    LeetCode 157. Read N Characters Given Read4
    LeetCode 317. Shortest Distance from All Buildings
    LeetCode Smallest Rectangle Enclosing Black Pixels
    LeetCode 315. Count of Smaller Numbers After Self
    LeetCode 332. Reconstruct Itinerary
    LeetCode 310. Minimum Height Trees
    LeetCode 163. Missing Ranges
    LeetCode Verify Preorder Serialization of a Binary Tree
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/8203616.html
Copyright © 2011-2022 走看看