iOS:block 的使用与详解

326 阅读6分钟

本文创作是为了知识温习和巩固,并希望对大家能够有所帮助。如果发现有任何错误,肯请大家留言指正,谢谢🙏。

一、什么是block ?

Block: 是将函数及其执行上下文封装起来的对象。
闭包:一个存在函数内部的引用关系指向外部函数的局部变量对象。 闭包作用:延长外部函数变量对象的生命周期,使闭包从函数外部访问函数内部的私有变量。

其实闭包与block很像(都是对外部变量进行捕获,外部失效时,内部还保留一份状态),可以说block就是Objective-C对闭包的实现。

二、block 底层实现

    int age = 20; 
    void (^block)(void) = ^{
        NSLog(@"age is %d",age); 
    }; 
    block(); 

    // 编译之后
    // block的定义
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
    // block的调用
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

block的本质就是一个结构体对象,结构体__main_block_impl_0(main:文件名;block:方法名)代码如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
    //构造函数(类似于OC中的init方法) _age是外面传入的
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    //isa指向_NSConcreteStackBlock 说明这个block就是_NSConcreteStackBlock类型的(根据不同类型变换)
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

结构体中第一个是struct __block_impl impl;
block 内部有 isa 指针,所以说其本质也是 OC 对象

struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr; 
};       

结构体:__main_block_desc_0;

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size; // 结构体__main_block_impl_0 占用的内存大小
}

三、block 的几种形势?

1、底层代码分析需要一直编译我们可以根据代码实践及地址的防止来说明
2、block的三种类型:

  • NSGlobalBlock(全局block)
  • NSMallocBlock(堆区block)
  • NSStackBlock (栈区block)
    其中栈 Block 存储在栈(stack)区,堆 Block 存储在堆(malloc)区,全局 Block 存储在已初始化数据(.data) 区

栈区block 在没有进行copy的时候block所在地区域(使用外部变量切没有进行copy操作的block这个时候就是在栈区)

NSInteger num = 50;
NSLog(@"%@",[^{NSLog(@"==____%p",&num);} class]);
// 输出结果:
2022-07-25 16:25:33.571669+0800 Test[2090:1410259] __NSStackBlock__

2、对栈 block 进行 copy 操作,就是堆 block

    NSString * str = @"student";
    NSLog(@"s1===%p",&str);
    void(^myBlock2)(void) = ^() {
        NSLog(@"str2 ===== %@",str);
        NSLog(@"s2=====%p",&str);
    };

    str = @"asdfq23";
    NSLog(@"str3 ===== %@",str);
    NSLog(@"s3===%p",&str);
    myBlock2();
    NSLog(@"block:%@",[myBlock2 class]);
    // 输出结果
    2022-07-25 16:33:40.543281+0800 Test[2095:1412853] s1===0x16f539b08
    2022-07-25 16:33:40.543369+0800 Test[2095:1412853] str3 ===== asdfq23
    2022-07-25 16:33:40.543434+0800 Test[2095:1412853] s3===0x16f539b08
    2022-07-25 16:33:40.543495+0800 Test[2095:1412853] str2 ===== student
    2022-07-25 16:33:40.543554+0800 Test[2095:1412853] s2=====0x2802c8bf0
    2022-07-25 16:33:40.543620+0800 Test[2095:1412853] block:__NSMallocBlock__
    这个时候进行了值拷贝这个时候是在堆区
    [self testblock:^{
        NSLog(@"%@",self);
    }]; 
    - (void)testblock:(dispatch_block_t)block {
    block();
    dispatch_block_t testblock = block;
    NSLog(@"%@====%@",[block class],[testblock class]);
    
    // 输出结果
    2022-07-25 16:47:43.088125+0800 Test[2105:1417805] __NSStackBlock__====__NSMallocBlock__
}

全局block 呢 我们做一个全局变量就行了

   int b = 10;
   @implementation ViewController
   
   NSLog(@"blockStr=====%p",&b);
   void(^myBlock3)(void) = ^() {
        NSLog(@"blockStr ===== %d",b);
        NSLog(@"blockStr=====%p",&b);
    };
    myBlock3();
    NSLog(@"block:%@",[myBlock3 class]);
    2022-07-25 16:40:30.897293+0800 Test[2098:1414929] blockStr=====0x100d46b50
    2022-07-25 16:40:30.897407+0800 Test[2098:1414929] blockStr ===== 10
    2022-07-25 16:40:30.897518+0800 Test[2098:1414929] blockStr=====0x100d46b50
    2022-07-25 16:40:30.897669+0800 Test[2098:1414929] block:__NSGlobalBlock__
    这个时候打印的便是全局block block在调用全局变量的时候直接拷贝的是指针地址而非值的获取,所以地址都是一样的。

即如果对栈Block进行copy,将会copy到堆区,对堆Block进行copy,将会增加引用计数,对全局 Block 进行 copy,因为是已经初始化的,所以什么也不做。

注:当有__block修饰变量的时候这个时候在编译的时候 有个_forwarding 修饰符。
_forwarding:__block 变量在 copy 时,由于__forwarding的存在,栈上的 __forwarding指针会指向堆上的__forwarding变量,而堆上的__forwarding指针指向其自身,所以,如果对__block的修改,实际上是在修改堆上的__block变量。

__forwarding 指针存在的意义就是,无论在任何内存位置,都可以顺利地访问同一个__block 变量

另外由于block捕获的__block修饰的变量会去持有变量,那么如果用__block修饰self,且self 持有 block,并且 block 内部使用到__block 修饰的 self 时,就会造成多循环引用,即 self 持有 block, block 持有__block 变量,而__block 变量持有 self,造成内存泄漏。

__weak typeof(self) weakSelf = self;
    [self testblock:^{
        NSLog(@"%@",weakSelf.blockStr);
  }];

如果要解决这种循环引用,可以主动断开__block 变量对 self 的持有,即在 block 内部使用完 weakself 后,将其置为 nil,但这种方式有个问题,如果 block 一直不被调用,那么循环引用将一直存在。 所以,我们最好还是用__weak 来修饰 self

四、block的循环引用

上面已经说了我们如果在写代码的时候如果self是有这个block 而 我们调用的时候呢 block也同时持有self 这个时候就会造成循环引用。我们通常的做发就是上面的进行若引用 __weak typeof(self) weakSelf = self; 但是这个并不是最好的,如果block self被提前释放了 这个时候我们的代码就会出现问题。

#import "TestObj.h"

@implementation TestObj
- (id) init {

    if (self = [super init]) {

        __weak typeof(self) weakSelf = self;

        self.testBlock = ^{
            dispatch_async(dispatch_get_global_queue(0, 0), ^{

                [NSThread sleepForTimeInterval:1];
                NSLog(@"%@",weakSelf);
            });
        };

    }
    return self;
}
@end

// 在另一个类调用
TestObj *obj = [[TestObj alloc] init];
obj.testBlock();

执行方法之后,执行的结果(null):2022-07-25 17:42:27.170195+0800 Test(null),block是异步执行的,在block的代码执行之前 我们执行的 obj.testBlock(); 已经结束这个时候obj已经被释放,所以是null,怎么解决呢:就是对weakself 再进行一遍 _strong就可以了

#import "TestObj.h"

@implementation TestObj
- (id) init {

    if (self = [super init]) {

        __weak typeof(self) weakSelf = self;

        self.testBlock = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
            dispatch_async(dispatch_get_global_queue(0, 0), ^{

                [NSThread sleepForTimeInterval:1];
                NSLog(@"%@",weakSelf);
            });
        };
    }
    return self;
}
@end

2022-07-25 17:49:50.848136+0800 Test[2121:1434176] <TestObj: 0x28260c510>

其实我们有很多的第三方对此都有不同的封装和用法给他家推荐yykit里的weakify方法,大家有兴趣可以研究下

#ifndef weakify
    #if DEBUG
        #if __has_feature(objc_arc)
        #define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object;
        #else
        #define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object;
        #endif
    #else
        #if __has_feature(objc_arc)
        #define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object;
        #else
        #define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object;
        #endif
    #endif
#endif


#ifndef strongify
    #if DEBUG
        #if __has_feature(objc_arc)
        #define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object;
        #else
        #define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object;
        #endif
    #else
        #if __has_feature(objc_arc)
        #define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object;
        #else
        #define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object;
        #endif
    #endif
#endif