Block

  • block本质上也是一个OC对象,它内部也有一个isa指针。
  • block是封装了函数调用以及函数调用环境的OC对象。
    Block底层结构.png
  • block底层结构就是__main_block_impl_0结构体,内部包含了impl结构体和Desc结构体以及外部需要访问的变量,block将需要执行的代码放到一个函数里,impl内部的FuncPtr指向这个函数的地址,通过地址调用这个函数,就可以执行block里面的代码了。Desc用来描述block,内部的reserved作保留,Block_size描述block占用内存。

block的变量捕获(capture)

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

  • 由于作用域的问题,全局变量不用捕获到block内部,block执行代码的函数可直接访问
  • 局部变量的定义和需要执行的代码在不同的函数里,如果想跨函数访问局部变量,需要把局部变量捕获到block中存起来,再在执行代码的函数从block中取出捕获的局部变量进行访问。


    block的变量捕获.png
auto int age = 10;
static int height = 10;    
void (^block)(void) = ^{
    NSLog(@"age is %d,height is %d",age,height);
};        
age = 20;
height = 20;        
block();
-------------------------------------------------
output: age is 10,height is 20
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age; // 值传递
  int *height; // 指针传递
}

auto变量和static变量访问方式的不同,是由于auto变量随时可能自动销毁,通过值传递访问;而static变量会一直在内存中,可通过指针(地址)传递访问。

  • 同样的,self也会被block捕获,是因为所有的OC方法转化成C语言函数,底层会默认传递两个参数,self_cmd,也是局部变量。
- (void)test
{
    void(^block)(void) = ^{
        NSLog(@"----%p",self);
    };
    block();
}
-------------------------------------------------
static void _I_YCPerson_test(YCPerson * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__YCPerson__test_block_impl_0((void *)__YCPerson__test_block_func_0, &__YCPerson__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

struct __YCPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __YCPerson__test_block_desc_0* Desc;
  YCPerson *self;
};

block的类型

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

__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
block类型 环境
NSGlobalBlock 没有访问auto变量
NSStackBlock 访问了auto变量
NSMallocBlock NSStackBlock调用了copy

每一种类型的block调用copy后的结果如下所示

Block的类 副本源的配置存储域 复制效果
_NSConcreteGlogalBlock 程序的数据区域 什么也不做
_NSConcreteStackBlock 从栈复制到堆
_NSConcreteMallocBlock 引用计数器增加
  • 在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
    • block作为函数返回值时
    • 将block赋值给__strong指针时
    • block作为Cocoa API中方法名含有usingBlock的方法参数时
    • block作为GCD API的方法参数时

对象类型的auto变量

  • 在使用clang转换OC代码为C++代码时,可能会遇到以下问题:
    cannot create __weak reference in file using manual reference
    解决方案:支持ARC、指定运行时系统版本,比如
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc-fobjc-arc -fobjc-runtime=ios-8.0.0main.m
YCPerson *person = [[YCPerson alloc] init];
person.age = 10;        
__weak YCPerson *weakPerson = person;
    MyBlock block = ^{
    NSLog(@"-----%d",weakPerson.age);
};
-------------------------------------------------
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  YCPerson *__weak weakPerson;
}
YCPerson *person = [[YCPerson alloc] init];
person.age = 10;
//        __weak YCPerson *weakPerson = person;
MyBlock block = ^{
    NSLog(@"-----%d",person.age);
};
-------------------------------------------------
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  YCPerson *__strong person;
}

当block内部访问了对象类型的auto变量时

  • 如果block是在栈上,不论是ARC还是MRC环境,或者对外部的auto变量是强引用还是弱引用,都不会对auto变量产生强引用。
  • 如果block被拷贝到堆上
    1、会调用block内部的copy函数
    2、copy函数内部会调用_Block_object_assign函数
    3、_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak)产生强引用还是弱引用。
  • 如果block从堆上移除
    1、会调用block内部的dispose函数
    2、dispose函数内部会调用_Block_object_dispose函数
    3、_Block_object_dispose函数会自动释放引用的auto变量,类似于release
函数 调用时机
copy函数 栈上的Block复制到堆上
dispose函数 堆上的Block被废弃时

__block修饰符

  • 一般情况下,对被截获对象进行赋值操作需要添加__block修饰符(赋值≠使用)
  • 对变量进行赋值时
    • 对局部变量(基本数据类型对象类型),需要__block修饰符
    • 静态局部变量全局变量静态全局变量,不需要__block修饰符
  • __block可以用来解决block内部无法修改auto变量的问题
  • 编译器会将__block变量包装成一个对象(__Block_byref_age_0结构体),结构体内部__forwarding是指向自身的指针,内部还存储着外部auto变量的值
    __block修饰基本数据类型
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 三种auto变量
        int no = 20; // 基本数据类型的auto变量,不需要内存管理,不会生成copy函数和dispose函数

        __block int age = 10; // __block修饰基本数据类型变量,需要内存管理,会生成copy函数和dispose函数

        NSObject *object = [[NSObject alloc] init]; // 对象类型的auto变量,需要内存管理,不会生成copy函数和dispose函数
        __weak NSObject *weakObject = object;
     
        // 刚开始block内存在栈上,在ARC环境下,一旦block被强引用着,会对栈上的block进行copy操作,会拷贝到堆上
        // block如果是在栈上,对象类型的auto变量object和__block变量age产生的都是弱引用,不是强引用;如果block被copy到堆时,都会通过copy函数来处理它们
        MyBlock block = ^{ 
            age = 20; // 修改age变量
            NSLog(@"%d",no);
            NSLog(@"%d",age);
            NSLog(@"%p", weakObject);
        };
        block();
    }
    return 0;
}
-------------------------------------------------
struct __Block_byref_age_0 {
    void *__isa;
    struct __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

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

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    struct __Block_byref_age_0 *age;
};

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age; // 结构体内部会存储着age值
};

  // 此处block会捕获三个auto变量
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int no; // 将no值直接存储
  NSObject *__weak weakObjc; // 访问对象类型的auto变量,会在内部存储该类型的指针变量,__weak or __strong取决于外部如何访问
  __Block_byref_age_0 *age; // 访问__Block变量,会将age包装到__Block_byref_age_0结构体中,block内部保留一个引用这个结构体的指针,指针会指向__Block_byref_age_0结构体
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObjc, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObjc(_weakObjc), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // 通过block中age指针拿到指向结构体的指针
  int no = __cself->no; // bound by copy
  NSObject *__weak weakObjc = __cself->weakObjc; // bound by copy
            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_0,no);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_1,(age->__forwarding->age));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_2,weakObjc);
        }

// block从栈拷贝到堆时调用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
// __block,第三个参数传递8(BLOCK_FIELD_IS_BYREF),__block变量不存在强弱引用之分,就是强引用
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
// 对象类型的auto变量object,第三个参数传递3(BLOCK_FIELD_IS_OBJECT),如果外部通过弱(强)引用访问OC对象,那_Block_object_assign对OC对象产生的就是弱(强)引用
_Block_object_assign((void*)&dst->weakObjc, (void*)src->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

// block从堆中移除调用
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

// 访问对象类型的auto变量,会生成以下两个函数,对内部访问的对象进行内存管理操作,访问基本数据类型不会生成这两个函数
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};  

__block的forwarding指针

__block的forwarding指针
(age->__forwarding->age) = 20;
  • 上,__block结构体中的__forwarding指针指向自己,一旦复制到上,栈上的__block结构体中的__forwarding指针会指向堆上的__block结构体,堆上__block结构体中的__forwarding还是指向自己。假设age是栈上的变量,age->__forwarding会拿到堆上的__block结构体,age->__forwarding->age会把20赋值到堆上,不论是栈上还是堆上的__block结构体,都能保证20赋值到堆的结构体里。

block循环引用问题

  • 用__weak、__unsafe_unretained解决
// __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil,若再次访问此变量,不会产生错误。
__weak typeof(self) weakSelf = self;
   self.block = ^{
   NSLog(@"%d",weakSelf.age);
};
// __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变,若再次访问此变量,容易产生野指针错误。
__unsafe_unretained typeof(self) weakSelf = self;
   self.block = ^{
   NSLog(@"%d",weakSelf.age);
};
__weak、__unsafe_unretained解决循环引用.png
  • 用__block解决
__block YCPerson *person = [[YCPerson alloc] init];
person.age = 10;
person.block = ^{
    NSLog(@"%d",person.age);
    person = nil;
};
person.block(); // 必须要调用block,执行block内代码,将对象置为nil
用__block解决循环引用.png

你可能感兴趣的:(Block)