天天看点

iOS - Block原理Block本质Block类型Block变量捕获机制

Block本质

1.block本质上是一个封装了函数调用以及调用环境的OC对象,它的内部也有个isa指针

2.block底层结构如下

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;   // -> block占用内存大小
    
    // copy,dispose函数管理自动管理对象内存
    void (*copy)(struct __main_block_impl_0*,struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);    
};

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr; // -> 函数指针
};

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;
    };
    //...捕获值
};
           

Block类型

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

1._ _ NSGlobalBlock _ _ (_ NSConcreteGlobalBlock)

内存区域:.data区域(数据区域)

环境:没有访问auto变量

调用copy:什么也不做

2._ _ NSStackBlock _ _ (_ NSConcreteStackBlock)

内存区域:栈区

环境:访问auto变量

调用copy:从栈复制到堆

3._ _ NSMallocBlock _ _ (_ NSConcreteMallocBlock)

内存区域:堆区

环境:_ _ NSStackBlock _ _调用了copy

调用copy:引用计数增加

void (^block)(void) = ^{
        NSLog(@"hello world!");
};
        
NSLog(@"%@",[block class]);                                             // ==> __NSGlobalBlock__
NSLog(@"%@",[[block class] superclass]);                                // ==> __NSGlobalBlock
NSLog(@"%@",[[[block class] superclass] superclass]);                   // ==> NSBlock
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);      // ==> NSObject

           

Block变量捕获机制

局部变量:

{

auto -> 捕获到block内部,值传递

static -> 捕获到block内部,指针传递

}

全局变量:未捕获到block内部,直接访问

Block的copy

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

-> block作为函数返回值时

-> 将block赋值给__strong指针时

-> block作为Cocoa API中方法名含有usingBlock的方法参数时

-> block作为GCD API的方法参数时

_block修饰符

_block可以用于解决block内部无法修改auto变量值的问题

_block不能修饰全局变量、静态变量(static)

编译器会将_block变量包装成一个对象

//__block修饰的对象的结构体
struct __Block_byref_age_0 {
    void *__isa;
    __Block_byref_age_0 *__forwarding; //指向自身
    int __flages;
    int __size;
    int age;
};

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

__block int age = 10;
^{
        NSLog(@"%d",age);
}();
           

__block的 __ forwarding指针

iOS - Block原理Block本质Block类型Block变量捕获机制

__block的内存管理

当block在栈上时,并不会对__block变量产生强引用

当block被copy到堆时

1、会调用block内部的copy函数

2、copy函数内部会调用_Block_object_assign函数

3、_Block_object_assign函数会对__block变量形成强引用(retain)

iOS - Block原理Block本质Block类型Block变量捕获机制
iOS - Block原理Block本质Block类型Block变量捕获机制

当block从堆中移除时

1、会调用block内部的dispose函数

2、dispose函数内部会调用_Block_object_dispose函数

3、_Block_object_dispose函数会自动释放引用的__block变量(release)

iOS - Block原理Block本质Block类型Block变量捕获机制

对象类型的auto变量、_block变量

1.当block在栈上时,对它们都不会产生强引用

2.当block拷贝到堆上时,都会通过copy函数来处理它们

__block变量(假设变量名叫做a)

__Block_object_assign((void*)&dst->a,(void*)src->a,8/__BLOCK_FIELD_IS_BYREF/);

对象类型的auto变量(假设变量名叫做p)

__Block_object_assign((void*)&dst->p,(void*)src->p,3/__BLOCK_FIELD_IS_OBJECT/);

__Block_object_assign函数会根据所指向对象的修饰符(__strong,__weak,__unsafe_unretained)做出相应的操作,形成强引用(retain)或弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)

3.当block从堆上移除时,都会通过dispose函数来释放它们

__block变量(假设变量名叫做a)

__Block_object_dispose((void*)src->a,8/__BLOCK_FIELD_IS_BYREF/);

对象类型的auto变量(假设变量名叫做p)

__Block_object_dispose((void*)src->p,3/__BLOCK_FIELD_IS_OBJECT/);

__Block_object_dispose会自动释放指向的对象(release)

解决循环引用问题 - ARC

// 用__weak,__unsafe_unretained解决
// __weak:不会产生强引用,指向对象销毁时,会自动让指针指向nil
// __unsafe_unretained:不会产生强引用,不安全,指向对象销毁时,指针存储的地址值不变
    __weak typeof (self)weakself = self;
    self.block = ^{
        NSLog(@"%@",weakself);
    };
    
    __unsafe_unretained id weakself = self;
    self.block = ^{
        NSLog(@"%@",weakself);
    };
    
    // 用__block解决(必须要调用block)
    __block id weakself = self;
    self.block = ^{
        NSLog(@"%@",weakself);
        weakself = nil;
    };
    self.block();
}
           

解决循环引用问题 - MRC

// 用__unsafe_unretained解决
    __unsafe_unretained id weakself = self;
    self.block = ^{
        NSLog(@"%@",weakself);
    };
    
    // 用__block解决(必须要调用block)
    __block id weakself = self;
    self.block = ^{
        NSLog(@"%@",weakself);
        weakself = nil;
    };
}
           

block的属性修饰词为什么是copy?使用block有哪些使用注意?

1.block一旦没有进行copy操作,就不会在堆上

2.使用注意:循环引用问题