美文网首页
iOS中的Block

iOS中的Block

作者: 皆为序幕_ | 来源:发表于2018-09-11 11:40 被阅读0次

    什么是Block(快速实现直接输入inlink)

    Block是一种特殊的数据类型

    Block的作用

    • 用于保存一段代码,可以在恰当的时间取出来调用
    • 功能类似于函数和方法

    Block的格式

    返回值(^block变量名)(形参列表) =  ^( 形参列表){
    
    };
    
    • 无参数无返回值
      void (^sunBlock)();
      sunBlock = ^{
        NSLog(@"sunBlock");
      };
      sunBlock();
      
    • 有参数无返回值
      void(^sunBlock)(int,int);
      sunBlock = ^(int value1,int value2){
         NSLog(@"%d",value1 + value2);
      };
      sunBlock(10,20);
      
    • 有参数有返回值
      int (^sunBlock)(int,int);
      sunBlock = ^(int value1,int value2){
          return value1 + value2;
      };
      
      NSLog(@"%d",sunBlock(10,20));
      
      

    typedef 和Block

    利用typedef给block起别名,和指向函数的指针一样,block变量的名称就是别名

    
    typedef int (^calculateBlock)(int,int);
    int main(int argc, const char * argv[]) {
        
        calculateBlock sumBlock  = ^(int value1,int value2){
            return value1 + value2;
        };
        NSLog(@"%d",sumBlock(20,10));
            
        calculateBlock minusBlock  = ^(int value1,int value2){
            return value1 - value2;
        };
        NSLog(@"%d",minusBlock(20,10));
    }
    
    

    Block的底层实现

    • 原文件:
    int main(int argc, const char * argv[]) {
        ^{ };
        return 0;
    }
    
    • 通过clang命令将OC转为C++代码来查看一下Block底层实现,clang命令使用方式为终端使用cd定位到main.m文件所在文件夹,然后利用clang -rewrite-objc main.m将OC转为C++,成功后在main.m同目录下会生成一个main.cpp文件
        struct __block_impl {
           void *isa; //isa,指向所属类的指针,也就是block的类型
           int Flags; //flags,标志变量,在实现block的内部操作时会用到
           int Reserved; //Reserved,保留变量
           void *FuncPtr; //block执行时调用的函数指针
       };
    
       struct __main_block_impl_0 {
         struct __block_impl impl;
         struct __main_block_desc_0* Desc;
         __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
           impl.isa = &_NSConcreteStackBlock;  //__main_block_impl_0的isa指针指向了_NSConcreteStackBlock
           impl.Flags = flags;
           impl.FuncPtr = fp; //从main函数中看, __main_block_impl_0的FuncPtr指向了函数__main_block_func_0
           Desc = desc; //__main_block_impl_0的Desc也指向了定义__main_block_desc_0时就创建的__main_block_desc_0_DATA,其中纪录了block结构体大小等信息。
             }
       };
    
           static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
       }
    
        static struct __main_block_desc_0 {
             size_t reserved; //保留字段
         size_t Block_size; //block大小(sizeof(struct __main_block_impl_0))
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    //以上代码在定义__main_block_desc_0结构体时,同时创建了__main_block_desc_0_DATA,并给它赋值,以供在main函数中对__main_block_impl_0进行初始化。
    
       int main(int argc, const char * argv[]) {
    
           ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
           return 0;
       }
    

    1、__block_impl结构体,它包含了isa指针(包含isa指针的皆为对象),也就是说block也是一个对象
    2、__main_block_impl_0结构体,可以看出是根据所在函数(main函数)以及出现序列(第0个)进行命名的,如果是全局的blcok,就根据变量名和出现序列进行命名。
    3、__main_block_impl_0中包含了俩个成员变量和一个构造函数,成员变量分别是__block_impl结构体和描述信息Desc,之后在构造函数中初始化block的类型信息和函数指针等信息。
    4、__main_block_func_0函数,其实对应的block的函数体,该函数接受了一个__cself参数,其实就是对应的block本身
    5、__main_block_desc_0结构体,其中比较有价值的信息是block的大小
    6、main函数对block的创建,可以看出执行block就是调用一个以block自身为参数的函数,这个函数对应着block的执行体。

    Block的分类

    • NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
    • NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
    • NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁。
    NSConcreteGlobalBlock 类型的 block 的实现
    void (^testGlobalBlock)() = ^{
        NSLog(@"hello block");
    };
    int main(int argc, const char * argv[]) {
        testGlobalBlock();
        return 0;
    }
    

    testGlobalBlock的isa指向了_NSConcreteGlobalBlock,即在全局区域创建,block变量存储在全局数据存储区

    NSConcreteStackBlock 类型的 block 的实现
    int main(int argc, const char * argv[]) {
        void (^testStackBlock)() = ^{
            NSLog(@"hello block");
        };
        testStackBlock();
        return 0;
    }
    

    testStackBlock的isa指向了_NSConcreteStackBlock,即在栈区创建。

    NSConcreteMallocBlock 类型的 block 的实现
    int main(int argc, const char * argv[]) {
       void (^testStackBlock)() = [^{
            NSLog(@"hello block");
        } copy];
        testStackBlock();
        return 0;
    }
    

    NSConcreteMallocBlock 类型的 block 通常不会在源码中直接出现,其需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。

    其内部通过函数memmove将栈中的block的内容拷贝到了堆中,并使isa指向了_NSConcreteMallocBlock。

    block主要的一些学问就出在栈中block向堆中block的转移过程中了。

    Block的应用

    Block中访问局部变量
    • 在Block中访问局部变量
    int main(int argc, const char * argv[]) {
        int testNum = 10;
        void(^testNumBlock)() = ^{
            NSLog(@"%d",testNum);
        };
        testNumBlock();
        return 0;
    }
    打印结果:10
    
    • 在声明Block之后,调用Block之前对局部变量进行修改,在调用Block时局部变量值是修改之前的旧值
    int main(int argc, const char * argv[]) {
        int testNum = 10;
        void(^testNumBlock)() = ^{
            NSLog(@"%d",testNum);
        };
        testNum = 20;
        testNumBlock();
        return 0;
    }
    打印结果:10
    
    • 在Block中不可以直接修改局部变量
    int main(int argc, const char * argv[]) {
        int testNum = 10;
        void(^testNumBlock)() = ^{
            testNum = 20; //报错
            NSLog(@"%d",testNum);
        };
        testNumBlock();
        return 0;
    }
    
    Block内访问__block修饰的局部变量
    • 在局部变量前使用下划线下划线block修饰,在声明Block之后、调用Block之前对局部变量进行修改,在调用Block时局部变量值是修改之后的新值
    __block int testNum = 10;
        void(^testNumBlock)() = ^{
            NSLog(@"%d",testNum);
        };
        testNum = 20;
        testNumBlock();
    打印结果:20
    
    • 在局部变量前使用下划线下划线block修饰,在Block中可以直接修改局部变量
    int main(int argc, const char * argv[]) {
        __block int testNum = 10;
        void(^testNumBlock)() = ^{
            testNum = 20;
            NSLog(@"%d",testNum);
        };
        testNumBlock();
        return 0;
    }
    打印结果:20
    
    Block内访问全局变量
    • 在Block中可以访问全局变量
    int testNum = 10;
    int main(int argc, const char * argv[]) {
        void(^testNumBlock)() = ^{
            NSLog(@"%d",testNum);
        };
        testNumBlock();
        return 0;
    }
    打印结果:10
    
    • 在声明Block之后、调用Block之前对全局变量进行修改,在调用Block时全局变量值是修改之后的新值
    int testNum = 10;
    int main(int argc, const char * argv[]) {
        void(^testNumBlock)() = ^{
            NSLog(@"%d",testNum);
        };
        testNum = 20;
        testNumBlock();
        return 0;
    }
    打印结果:20
    
    • 在Block中可以直接修改全局变量
    int testNum = 10;
    int main(int argc, const char * argv[]) {
        void(^testNumBlock)() = ^{
            testNum = 20;
            NSLog(@"%d",testNum);
        };
        testNumBlock();
        return 0;
    }
    打印结果:20
    
    Block内访问静态变量
    • 在Block中可以访问静态变量
    int main(int argc, const char * argv[]) {
        static int testNum = 10;
        void(^testNumBlock)() = ^{
            NSLog(@"%d",testNum);
        };
        testNumBlock();
        return 0;
    }
    打印结果:10
    
    • 在声明Block之后、调用Block之前对静态变量进行修改,在调用Block时静态变量值是修改之后的新值
    int main(int argc, const char * argv[]) {
        static int testNum = 10;
        void(^testNumBlock)() = ^{
            NSLog(@"%d",testNum);
        };
        testNum = 20;
        testNumBlock();
        return 0;
    }
    打印结果:20
    
    • 在Block中可以直接修改静态变量
    int main(int argc, const char * argv[]) {
        static int testNum = 10;
        void(^testNumBlock)() = ^{
            testNum = 20;
            NSLog(@"%d",testNum);
        };
        testNumBlock();
        return 0;
    }
    打印结果:20
    
    Block作为参数传递
    typedef void(^TestBlock)();
    NSMutableArray *array;
    void test(){    
        int a = 10;
        TestBlock blcok = ^{
            NSLog(@"%d",a);
        };
        [array addObject:blcok];
        NSLog(@"%@",blcok);
    }
    
    int main(int argc, const char * argv[]) {
    
        array = [[NSMutableArray alloc]init];
        test();
        TestBlock blockk = [array lastObject];
        blockk();
        NSLog(@"%@",blockk);
        return 0;
    }   
    结果:
    在ARC下:
     test2[2423:124143] <__NSMallocBlock__: 0x1004037f0>
     test2[2423:124143] 10
     test2[2423:124143] <__NSMallocBlock__: 0x1004037f0>
    在非ARC下:
    程序崩溃 
    test2[2449:125851] <__NSStackBlock__: 0x7fff5fbff6f8>
    

    1、在非ARC下,TestBlock的isa指向NSStackBlock,当函数退出后,相应的堆被销毁,block也就不存在了,在经过copy或retain之后,对象的类型从NSStackBlock变为了NSMallocBlock,在函数结束后依然可以访问,在非ARC环境下,copy或retain了block后一定要在使用后release,不然会有内存泄露,而且泄露点是在系统级,在Instruments里跟不到问题触发点,比较上火。

    2、ARC情况下,系统会将捕获了外部变量的block进行了copy。所以返回类型为NSMallocBlock,在函数结束后依然可以访问

    如果把blcok中的代码不再访问变量:

    TestBlock blcok = ^{
            NSLog(@"demo");
        };
    结果:
    ARC和非ARC得结果一致
    test2[2484:128052] <__NSGlobalBlock__: 0x100005290>
    test2[2484:128052] demo
    test2[2484:128052] <__NSGlobalBlock__: 0x100005290>
    
    Block作为返回值
    • 非ARC中
    - (testBlcok) myTestBlock {
        __block int val = 10;
        return ^{
            NSLog(@"val = %d", val);
        };
    }
    结果:Xcode就会提示报错Returning block that lives on the local stack
    

    在向外传递block的时候一定也要做到,传给外面一个在堆上的,autorelease的对象。

    - (testBlcok) myTestBlock {
        __block int val = 10;
        return [[^{
            NSLog(@"val = %d", val);
        } copy] autorelease];
    }
    
    • ARC中
    - (testBlcok) myTestBlock {
        __block int val = 10;
        return ^{
            NSLog(@"val = %d", val);
        };
    }
    结果:正常
    

    在ARC环境下,当block作为参数返回的时候,block也会自动被移到堆上。

    Block作为属性

    ARC 和非ARC得声明一样

    @property (strong, nonatomic) TestBlock *strongBlock;
    @property (copy, nonatomic) TestBlock *copyBlock;
    

    Block在MRC及ARC下的内存管理

    Block在MRC下的内存管理
    • 默认情况下,Block的内存存储在栈中,不需要开发人员对其进行内存管理
    - (void)viewDidLoad {
        [super viewDidLoad];
        void(^testBlock)() = ^{
            NSLog(@"------");
        };
        testBlock();
    }
    结果:当testBlock变量出了作用域,testBlock的内存会被自动释放
    
    • 在Block的内存存储在栈中时,如果在Block中引用了外面的对象,不会对所引用的对象进行任何操作
    - (void)viewDidLoad {
        [super viewDidLoad];
        Student *stu = [[Student alloc]init]; 
        void(^testBlock)() = ^{
            NSLog(@"%@",stu);
        };
        testBlock();
        [stu release];
    }
    结果:Student可以正常释放
    
    • 如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,这时需要对其进行release操作来管理内存
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        void(^testBlock)() = ^{
            NSLog(@"testBlock");
        };
        testBlock();
        Block_copy(testBlock);
        Block_release(testBlock);
    }
    结果:Block正常释放
    
    • 如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行一次retain操作,即使在Block自身调用了release操作之后,Block也不会对所引用的对象进行一次release操作,这时会造成内存泄漏
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Student *stu = [[Student alloc]init];
        void(^testBlock)() = ^{
            NSLog(@"%@",stu);
        };
        testBlock();
        Block_copy(testBlock);
        Block_release(testBlock);
        [stu release];
    }
    结果:Student无法正常被释放,因为其在Block中被进行了一次retain操作
    
    • 如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行一次retain操作,为了不对所引用的对象进行一次retain操作,可以在对象的前面使用__block来修饰
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        __block Student *stu = [[Student alloc]init];
        void(^testBlock)() = ^{
            NSLog(@"%@",stu);
        };
        testBlock();
        Block_copy(testBlock);
        Block_release(testBlock);
        [stu release];
    }
    结果:Student可以正常释放
    
    • 如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用
    • 第一种情况
    @interface Student : NSObject
    @property (nonatomic,copy) void(^testBlock)();
    @end
       ----------------------------------------------
    @implementation Student
    -(void)dealloc{
       NSLog(@"%s",__func__);
       Block_release(_testBlock);
       [super dealloc];
    }
    @end
       ----------------------------------------------
    -(void)viewDidLoad {
       [super viewDidLoad];
       
       Student *stu = [[Student alloc]init];
       stu.testBlock = ^{
           NSLog(@"%@",stu);
       };
       stu.testBlock();
       [stu release];
    }
    结果:因为testBlock作为Student的属性,采用copy修饰符修饰(这样才能保证Block在堆里面,以免Block在栈中被系统释放),所以Block会对Student对象进行一次retain操作,导致循环引用无法释放
    
    • 第二种情况
      @interface Student : NSObject
    @property (nonatomic,copy) void(^testBlock)();
    -(void)resetBlock;
    @end
        ----------------------------------------------
    @implementation Student
    -(void)resetBlock{
      
        self.testBlock = ^{
            NSLog(@"%@",self);
        };
    }
    -(void)dealloc{
        NSLog(@"%s",__func__);
    
        Block_release(_testBlock);
        [super dealloc];
    }
    @end
        ----------------------------------------------
    -(void)viewDidLoad {
        [super viewDidLoad];
        
        Student *stu = [[Student alloc]init];
        [stu resetBlock];
        [stu release];
    }
    结果:Student对象在这里无法正常释放,虽然表面看起来一个alloc对应一个release符合内存管理规则,但是实际在resetBlock方法实现中,Block内部对self进行了一次retain操作,导致循环引用无法释放
    
    • 如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用,解决循环引用的办法是在对象的前面使用下划线下划线block来修饰,以避免Block对对象进行retain操作
      • 第一种情况
      @interface Student : NSObject
        @property (nonatomic,copy) void(^testBlock)();
        @end
        ----------------------------------------------
        @implementation Student
        -(void)dealloc{
            NSLog(@"%s",__func__);
            Block_release(_testBlock);
            [super dealloc];
        }
        @end
        ----------------------------------------------
        - (void)viewDidLoad {
            [super viewDidLoad];
             __block Student *stu = [[Student alloc]init];
            stu.testBlock = ^{
                NSLog(@"%@",stu);
            };
            stu.testBlock(); 
            [stu release];
            }
            结果:Student可以正常释放
                
      
      • 第二种情况
       @interface Student : NSObject
       @property (nonatomic,copy) void(^testBlock)();
       -(void)resetBlock;
       @end    
       ----------------------------------------------
       @implementation Student
       -(void)resetBlock{
      // 这里为了通用一点,可以使用__block typeof(self) stu = self;
       __block Student *stu = self;
       self.testBlock = ^{
           NSLog(@"%@",stu);
           };
       }
       -(void)dealloc{
           NSLog(@"%s",__func__);
           Block_release(_testBlock);
           [super dealloc];
       }
       @end
       ----------------------------------------------
       - (void)viewDidLoad {
           [super viewDidLoad];
            Student *stu = [[Student alloc]init];
           [stu resetBlock];
           [stu release];
       }
       结果:Student可以正常释放
      

    Block在ARC下的内存管理

    • 在ARC默认情况下,Block的内存存储在堆中,ARC会自动进行内存管理,程序员只需要避免循环引用即可
    - (void)viewDidLoad {
        [super viewDidLoad];
        void(^testBlock)() = ^{
            NSLog(@"testBlock");
        };
        testBlock();
    }
    结果:当Block变量出了作用域,Block的内存会被自动释放
    
    • 在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行强引用,但是在Block被释放时会自动去掉对该对象的强引用,所以不会造成内存泄漏
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Student *stu = [[Student alloc]init];
        void(^testBlock)() = ^{
            NSLog(@"%@",stu);
        };
        testBlock();
    }
    结果:Student可以正常释放
    
    • 如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用
      • 第一种情况
      
      @interface Student : NSObject
      @property (nonatomic,copy) void(^testBlock)();
      @end
         ----------------------------------------------
      @implementation Student
      -(void)dealloc{
         NSLog(@"%s",__func__);
      }
      @end
         ----------------------------------------------
      -(void)viewDidLoad {
         [super viewDidLoad];
          Student *stu = [[Student alloc]init];
          stu.testBlock = ^{
             NSLog(@"%@",stu);
         };
         stu.testBlock();
      }
      结果:因为testBlock作为Student的属性,采用copy修饰符修饰(这样才能保证Block在堆里面,以免Block在栈中被系统释放),所以Block会对Person对象进行一次强引用,导致循环引用无法释放
      
      • 第二种情况
       @interface Student : NSObject
      @property (nonatomic,copy) void(^testBlock)();
      - (void)resetBlock;
      @end
          ----------------------------------------------
          @implementation Student
          - (void)resetBlock
          {
              self.testBlock = ^{
                  NSLog(@"------%@", self);
              };
          }
          -(void)dealloc{
              NSLog(@"%s",__func__);
          }
          @end
          ----------------------------------------------
          - (void)viewDidLoad {
              [super viewDidLoad];
              Student *stu = [[Student alloc]init];
              [stu resetBlock];
          }
          结果:Student对象在这里无法正常释放,在testBlock方法实现中,Block内部对self进行了一次强引用,导致循环引用无法释放
          
      
    • 如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用,解决循环引用的办法是使用一个弱引用的指针指向该对象,然后在Block内部使用该弱引用指针来进行操作,这样避免了Block对对象进行强引用
    • 第一种情况
        @interface Student : NSObject
        @property (nonatomic,copy) void(^testBlock)();
        @end
        ----------------------------------------------
        @implementation Student
        -(void)dealloc{
            NSLog(@"%s",__func__);
        }
        @end
        ----------------------------------------------
        - (void)viewDidLoad {
        [super viewDidLoad];
        
        Student *stu = [[Student alloc]init];
        __weak typeof(stu) weakS = stu;
        
        stu.testBlock = ^{
            NSLog(@"------%@", weakS);
        };
       stu.testBlock();
        
        // Student对象在这里可以正常被释放
    }
    
    
    • 第二种情况
      @interface Student : NSObject
      @property (nonatomic,copy) void(^testBlock)();
      -(void)resetBlock;
      @end
          ----------------------------------------------
          @implementation Student
          -(void)resetBlock
          {
              //这里为了通用一点,可以使用__weak typeof(self) weakP = self;
              __weak Student *stu = self;
              self.testBlock = ^{
                  NSLog(@"------%@", self);
          };
      }
          -(void)dealloc{
              NSLog(@"%s",__func__);
          }
          @end
          ----------------------------------------------
          -(void)viewDidLoad {
              [super viewDidLoad];
              Student *stu = [[Student alloc]init];
              [stu resetBlock];
          }
          结果:Student可以正常释放
      
      

    注: 关于下划线下划线block关键字在MRC和ARC下的不同

    __block在MRC下有两个作用

    1. 允许在Block中访问和修改局部变量
    2. 禁止Block对所引用的对象进行隐式retain操作

    __block在ARC下只有一个作用

    1. 允许在Block中访问和修改局部变量

    相关文章

      网友评论

          本文标题:iOS中的Block

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