iOS底层笔记--Block

403 阅读8分钟

前言

本文属笔记性质,是对kirito_song冰凌天两位文章学习和转载

文章具体地址:
MJiOS底层笔记--Block
小码哥iOS学习笔记第八天: block的底层结构
小码哥iOS学习笔记第九天: block的类型
小码哥iOS学习笔记第十天: __block和block内存管理

什么事Block

  • Block是将函数及其执行上下文封装起来的对象
{
    int age = 20;
    void (^block)(int, int) =  ^(int a , int b){
        NSLog(@"this is a block! -- %d", age);
    };
    block(10, 10);
}

源码解析

  • 使用【clang -rewrite-objc xxx.m】查看编译后的文件内容
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc; //block描述信息(大小等)
      int age;  //封装了函数调用所需环境(内部定义了一个age变量)
      
      //c++的构造函数  age(_age)表示_age将会自动赋值给age
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
        int age = __cself->age; // 将block当初捕获的变量,赋值给执行函数
        //函数调用
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
    }
    
    struct __block_impl {
      void *isa;  //表明block也属于OC对象
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    //其中函数调用会被单独封装成__main_block_func_0方法。在block定义时,传入block结构体。
    void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

    //而在block结构体中,会被赋值给impl.FuncPtr = fp;,将函数地址存储在block内部。
    //最终,在调用block时,获取FuncPtr,传入参数执行调用。
    ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);

block的本质

封装了函数调用以及调用环境的OC对象

  1. block本质上也是OC对象,内部也存在isa指针
  2. block内部封装了函数调用,以及函数调用所需的环境(参数)

Block类型

  • block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

    • NSGlobalBlock ( _NSConcreteGlobalBlock )
    • NSStackBlock ( _NSConcreteStackBlock )
    • NSMallocBlock ( _NSConcreteMallocBlock )
  • 这三种类型在内存中分别存储在不同的区域

    • __NSGlobalBlock__存在于内存的数据区域(.data区)
    • __NSStackBlock__存在于内存的栈区
    • __NSMallocBlock__存在于内存的堆区

在MRC下

  • block来源(auto即局部变量)

    • 内部没有使用auto类型变量的block, 就是__NSGlobalBlock__类型
    • 内部使用了auto类型变量的block, 就是__NSStackBlock__类型
    • __NSStackBlock__类型的block调用copy后就是__NSMallocBlock__类型, 通过copy, 将block从栈区复制到了堆区
  • 对于不同类型的block,调用copy会有不同操作

    • __NSGlobalBlock__类型的block调用copy后类型不变,还是__NSGlobalBlock__类型(还在数据区)
    • __NSMallocBlock__类型的block调用copy后类型不变,还是__NSMallocBlock__类型(不会生成新的block, 原有引用计数+1)

在ARC下

  • 在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:

    • block作为函数返回值时
        MJBlock myblock()
        {
            return ^{
                NSLog(@"---------");
            };
        }
    
    • block赋值给__strong指针
        MJBlock block = ^{
            NSLog(@"---------%d", age);
        };
    
    • block作为Cocoa API中方法名含有usingBlock的方法参数时
        [@[] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
        }];
    
    • block作为GCD API的方法参数时
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
        });
    
    • __NSGlobalBlock__类型的block, 不管怎样类型都不会改变, 依然在数据区

截获变量

变量捕获

  1. 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
  2. c语言中的局部变量,默认都为auto变量。所以auto代指局部变量

局部变量

1. 基本数据类型

由于局部变量的生命超出作用域就会被销毁。为保证block能够正常执行,局部变量在被block捕获时,会将值传递给block的构造函数。(源码同上方源码解析)

2. 对象类型(ARC环境下)

  • block捕获对象类型的局部变量时。__main_block_desc_0结构体内部会多出__main_block_copy_0__main_block_dispose_0函数,在block移动到堆空间时堆对象进行适当的retainrelease
  • 对于对象而言,传递的都是指针。
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSObject *obj = [NSObject new]; //OC对象
            void (^block)(void) = ^{
                NSLog(@"obj is %@", obj); //捕获
            };
            obj = nil;
            block();
        }
        return 0;
    }
    
    
    <!--c++源码-->
    //block结构体
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      
      //如果外部为__weak内部也会为__weak
      NSObject * __strong obj; //对于局部变量,结构体中保存变量与原本相同。
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      NSObject *obj = __cself->obj; // bound by copy
    
      NSLog((NSString *)&__NSConstantStringImpl__var_folders_tz_hcmmb5t57v1cr81ydm6s5s140000gn_T_main_d73acf_mi_0, obj);
    }
    
    //当block从栈移动到堆中时,执行此方法。
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    
        //会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
        _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    //当block从堆中移除(释放)时,执行此方法。
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
        //release引用的变量
        _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    //block描述。比基本类型多了两个变量。就是上面两个方法
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    
    // main函数
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
            
            //在构造block时,将OC对象传入
            void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
    
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }

3.静态局部变量

由于静态变量的生命常驻于内存,但使用仅限于作用域内部。所以静态变量在被block捕获时,只要将指向变量值的指针(地址)传递给构造函数,即能保证block正常执行。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int b = 10;
        void (^block)(void) = ^{
            NSLog(@"height is %d", b); //height is 20
        };
        b = 20;
        block();
    }
    return 0;
}

<!--c++源码-->
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *b; //b为指针类型
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_b, int flags=0) :  b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int *b = __cself->b; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, (*b));
}

// main中构建block.将b的指针传递给构造函数
void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &b));

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

全局变量和静态全局变量

由于全局变量的生命常驻于内存,并且使用不受作用域限制。所以全局变量并不需要被捕获,在执行block的函数调用时,直接使用全局变量,即能保证block正常执行。

int age_ = 10;
static int height_ = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@"age_ is %d ,height_ is %d", age_,height_);
        };
        age_ = 20;
        height_ = 20;
        block();
    }
    return 0;
}

<!--c++源码-->
int age_ = 10;
static int height_ = 10;

// 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;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    // 执行函数中,直接使用全局变量
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_tz_hcmmb5t57v1cr81ydm6s5s140000gn_T_main_6f323a_mi_0, age_,height_);
}

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)};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));  //在执行block构造函数时,并未将全局变量传递进去
        age_ = 20;
        height_ = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

__block修饰符

  • __block可以解决block内部无法修改局部变量值的问题
  • __block不能修饰全局变量和静态变量(static), 只能修饰auto变量
  • 编译器会将__block变量包装成一个对象(__Block_byref_age_0),被声明的值作为对象的属性存在。
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            __block int age = 10;
            MJBlock block = ^{
                __strong int myage = age;
                age = 20;
                NSLog(@"age is %d", age);
            };
            block();
        }
        return 0;
    }
    
    //__block对象结构体
    struct __Block_byref_age_0 {
      void *__isa;
      __Block_byref_age_0 *__forwarding;
      int __flags;
      int __size;
      int age;
    };
    
    //block结构体
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      NSObject *p;
      
      //不再是int age;
      __Block_byref_age_0 *age; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_p, __Block_byref_age_0 *_age, int flags=0) : p(_p), age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    // block执行函数
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
        __Block_byref_age_0 *age = __cself->age; // bound by ref
        
        //从__Block_byref_age_0结构体中获得age变量,并且修改
        (age->__forwarding->age) = 20;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0);
    }
    
    //c++ main函数
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        //__block int age = 10;
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        //等价于
        __Block_byref_age_0 age = {0,
                                    &age,
                                    0,
                                    sizeof(__Block_byref_age_0),
                                    10};
            
            
        //构建block结构体
        MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));
        //等价于
            MJBlock block = &__main_block_impl_0(__main_block_func_0,
                                             &__main_block_desc_0_DATA,
                                             p,
                                             &age,
                                             570425344);
    
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        
        }
        return 0;
    }

__forwarding指针

__forwarding指向__block所包装的对象实体,以确保使用时的正确性。

  • block在栈中,__forwarding所指实体为栈上的__block包装对象。

  • block移动到堆上,__block包装对象在堆中也会被复制一份。而二者的__forwarding指针都指向堆中的__block包装对象。

__block修饰OC对象

OC对象的强弱引用不会体现在block结构体中(都是strong),而是体现在__Block_byref结构体中。

 int main(int argc, const char * argv[]) {
        @autoreleasepool {
            MJPerson *person = [[MJPerson alloc] init];
            __block __weak JSPerson *weakPerson = person;
            MJBlock block = ^{
                NSLog(@"%p", weakPerson);
            };
            block();
        }
        return 0;
    }
    
    struct __Block_byref_weakPerson_0 {
         void *__isa; // 8
        __Block_byref_weakPerson_0 *__forwarding; // 8
         int __flags; // 4
         int __size; // 4
         void (*__Block_byref_id_object_copy)(void*, void*); // 当block被移动到堆,会对__Block_byref对象进行return(需要注意MRCblock进入堆中时不会retain该变量)
         void (*__Block_byref_id_object_dispose)(void*); // 当block从堆中移除,会对__Block_byref对象进行release
         MJPerson *__weak weakPerson; //__block包装的结构体中实际为weak引用。
    };
        
    struct __main_block_impl_0 {
         struct __block_impl impl;
         struct __main_block_desc_0* Desc;
         __Block_byref_weakPerson_0 *weakPerson; // // 依旧是strong引用
         __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
         impl.isa = &_NSConcreteStackBlock;
         impl.Flags = flags;
         impl.FuncPtr = fp;
         Desc = desc;
        }
    };