iOS block

2022-04-13  本文已影响0人  Edviin_2de8

什么是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中 局部变量可以分为

变量捕获
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

分析

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


Block的类型

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

应用程序的内存分配:
Block分为三种类型

Block的copy

在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修改变量

__block

  • __block 修饰auto变量,被修饰的变量被捕获后 变成了一个对象,在block就能做修改
  • __block不能修饰全局变量和static变量

Block与循环引用

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

解决循环引用
  • 使Block持有的对象指向我们声明的对象的指针式弱引用,那么我们外部指向实例对象的指针一旦被释放 实例对象就会被释放 那么Block就会被释放 从而解决循环引用。
__weak 和 __unsafe__unretained

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

__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

上一篇 下一篇

猜你喜欢

热点阅读