iOS block

作者: Edviin_2de8 | 来源:发表于2022-04-13 01:30 被阅读0次

什么是Block

Block 又称为“块” 或 “代码块”,作用是用来保存代码,保存在其内部的代码块 如果Block不被调用 这段代码就不会执行

在OC中Block的基本格式是这样的

返回值类型  (^block名)  (参数类型 和 数量) = ^(形参 和 数量){   
    //code 
};

block的本质

block的本质是对象,一个包含封装了函数调用和函数调用环境(比如参数和返回值)的对象

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

struct __block_impl {
    void *isa;//Block的类型
    int Flags;
    int Reserved;
    void *FuncPtr; //封装代码块的地址
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 构造函数(类似于OC的init方法),返回结构体对象
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

Block的本质了 就是__block_impl 包含了isa指针和代码块的执行地址 __main_block_desc_0 包含了block的大小。和一个相关Block的构造函数

局部变量和Block

OC中 局部变量可以分为

  • 自动变量 关键字为auto(值传递 可以省略 默认的 离开自己的作用域就会被销毁)
  • 静态变量 关键字 static(指针传递)
变量捕获
typedef  void(^myBlock) (void);
int weight = 88;
static int sex = 1 ;
int main(int argc, char * argv[]) {
    @autoreleasepool {
        int age = 10;
        static int height = 10;
        myBlock block = ^{
            NSLog(@"age is %d, height is %d,weight is %d,sex is %d", age, height,weight,sex);
            
        };
        weight = 77;
        sex = 2;
        height = 20;
        age = 20;
        block();
  }
    return 0;
}

输出结果age is 10, height is 20,weight is 77,sex is 2

分析
  • auto变量 age 值传递
    auto局部变量出了作用域就会销毁 为了避免执行block时 变量不存在 所以auto变量在Block内部被直接赋值为当前值。

  • static变量 height 指针传递
    static变量一直存在于内存中不必担心出了作用域就会销毁,所以存储的是变量指针,可以随时取出最新值。

  • 全局变量 sex weight 不捕获,直接取值
    全局变量 Block使用时不用捕获 直接访问 得到的全部都是最新值。

局部变量之所以需要捕获 因为我们是跨函数使用的 声明和使用不是在一个作用域内
注意的是self这个变量也是局部变量 也会被block捕获


Block的类型

block的最终的父类是NSObject, 也从侧面说明了Block的本质是一个对象

应用程序的内存分配:
  • 程序区域 .text区(代码段) 数据区域(.data区)【一般存放全局变量】
  • 堆区【alloc出来的对象 动态分配内存 需要程序员申请内存 也需要程序员管理内存】
  • 栈区【存放局部变量 系统自动分配内存 自动销毁内存】
Block分为三种类型
  • NSGlobalBlock(_NSConcreteGlobalBlock)(存放在数据区)
    不访问auto变量的block 即便是访问了static局部变量 或者全局变量
  • NSStackBlock(_NSConcreteStackBlock)(存放在栈区 系统管理内存)
    访问了auto变量
    捕获的变量都会因为block的释放被释放
  • NSMallocBlock(_NSConcreteMallocBlock) (存放在堆区 程序员管理内存)
    NSStackBlock调用了copy
    捕获的变量也会存储在堆区,只要我们不释放,就不会消失。虽然增加了我们管理内存的难度,但是也避免了调用时,数据释放的情况。

Block的copy
  • _NSConcreteGlobalBlock copy 还是全局block 什么也不做
  • _NSConcreteStackBlock copy 变成 _NSConcreteMallocBlock
  • _NSConcreteMallocBlock copy 引用计数加1

在ARC环境下 编译器会根据情况自动将栈上的Block复制到堆上 相当于对栈上的Block进行copy操作。

  1. Blcok 作为函数返回值的时候 会自动copy
// 定义Block
typedef void (^YZBlock)(void);

// 返回值为Block的函数
YZBlock myblock()
{
    int a = 6;
    return ^{
        NSLog(@"--------- %d",a);
    };
}

YZBlock Block = myblock();
Block();


  1. 将Block赋值给__strong强指针的时候 也会自动做copy操作
  2. Block作为GCD的参数时 也会被copy到堆上
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
            
});    
       
        
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //code to be executed after a specified delay
});

  1. Foundation框架下 block作为参数且方法名含有usingBlock时 会被自动copy

NSArray *array = @[@1,@4,@5];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            // code
}];


Block捕捉对象的auto变量
  • Block在栈上 不管是ARC 还是 MRC 都不会对捕获的auto变量进行强引用或者retain操作
  • 如果Block被拷贝到堆上
    • 会调用block内部的copy函数
    • copy函数内部调用_Block_object_assign函数 这个函数会根据auto变量的修饰符(__strong __weak)作出相应的操作 是强引用还是弱引用
  • 如果Block从堆上移除
    调用block内部的dispose函数 dispose函数内部会调用_Block_object_dispose函数 这个函数会自动断开引用的auto变量(断开这个引用) 相当于release
    block内部的copy函数和dispose函数 只会在捕获对象auto变量的时候才有(因为对象需要内存管理) 捕获简单的数据变量比如Int的时候 是没有的

Block修改变量
  • auto变量在Block内部不能修改
    因为当前函数和Block的代码块是两个独立的函数变量不能互相访问

__block

  • __block 修饰auto变量,被修饰的变量被捕获后 变成了一个对象,在block就能做修改
  • __block不能修饰全局变量和static变量
  • stataic变量在Block内部能修改
    因为static局部变量被捕获的是指向变量的指针,如果在block内部修改,相当于访问的是同一片地址,所以可以修改
  • 全局变量变量在Block内部能修改
    因为block不会捕获全局变量 无论在哪修改 都可以

Block与循环引用

循环引用的原因就是互相引用 导致双方都不能释放 造成内存泄漏

  • block内部使用实例对象 如果实例对象的修饰符是强引用 那么Block在拷贝到堆上时内部对实例对象也会产生一个强引用
  • 如果这个实例对象再持有了这个block 那么一定会产生循环引用的。
解决循环引用
  • 使Block持有的对象指向我们声明的对象的指针式弱引用,那么我们外部指向实例对象的指针一旦被释放 实例对象就会被释放 那么Block就会被释放 从而解决循环引用。
__weak 和 __unsafe__unretained

_weak 和_unsafe_unretained都能解决循环引用。
区别

  • 使用__weak 一旦我们的实例对象被释放,block内部持有的指向我们实例对象的指针会被指nil 不会产生野指针
  • 使用__unsafe__unretained block内部持有的指向我们实例对象的指针会还会指向我们已经释放的实例对象的地址 产生野指针。这也是它不安全的原因。
__weak为什么能打破循环引用?

一个变量一旦被__weak声明后,这个变量本身就是一个弱引用,只有在使用的那行代码里,才会临时增加引用计数,一旦那句代码执行完毕,引用计数马上-1,所以看起来的效果是,不会增加引用计数,block中也就不会真正持有这个变量了

__weak __typeof(self) weakSelf = self;
 
为什么有时候又需要使用__strong来修饰__weak声明的变量?
__strong typeof(self) strongSelf = weakSelf;

在block中使用__weak声明的变量,由于block没有对该变量的强引用,block执行的过程中,一旦对象被销毁,该变量就是nil了,会导致block无法继续正常向后执行。
使用__strong,会使得block作用区间,保存一份对该对象的强引用,引用计数+1,一旦block执行完毕,__strong变量就会销毁,引用计数-1
比如block中,代码执行分7步,在执行第二步时,weak变量销毁了,而第五步要用到weak变量。
而在block第一步,可先判断weak变量是否存在,如果存在,加一个__strong引用,这样block执行过程中,就始终存在对weak变量的强引用了,直到block执行完毕

Block的属性修饰词为什么是copy?

如果block不被copy 就是存储在栈空间上,我们就控制不了block的生命周期,可能我们使用的时候已经被释放了或者我们使用的时候 其内部捕获的变量已经释放了 导致程序错误。而拷贝到堆上,我们可以方便的控制其生命周期。虽然增加了管理内存的一些成本。但是可以减少错误。在ARC的情况下 如果有一个强引用指向Block 内部也会copy到堆上。使用strong也行。但是我们习惯使用copy

相关文章

网友评论

      本文标题:iOS block

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