iOS高级编程 --Block

2020-06-19  本文已影响0人  shengchang

概要

Blocks模式

语法(表达式/函数区别于类型变量)

^返回值类型(可省)+参数列表(可省)+表达式

^int (int count){
    return count + 1;
}

Block类型变量

//声明block类型变量
int (^blk)(int)
//函数指针
int(*funcptr)(int) = &func
//使用typedef简化类型变量
typedef int (^blk)(int)

截获自动变量值

在Block表达式中使用自动变量时,或截获保存其瞬间值,之后的改动不会影响截获值

__block说明符

在Block表达式中++重新赋值++自动变量(对象类型)时,会产生编译错误,需要声明__block类型

截获的自动变量

使用c语言数组会编译报错
const char text[] = "hello"
void (^blk)(void) = ^{
    char text = text[2];
}
Blocks中没有对c语言数组支持,使用指针可以解决
const char *text = "hello"
void (^blk)(void) = ^{
    char text = text[2];
}

Blocks实现

Block的实质

将oc代码转为c++代码的clang指令

clang -rewrite-objc 源文件名

block的本质是__main_block_impl_0(mian所在的函数名,0函数调用顺序)结构体实例的调用,结构体为

struct __main_block_impl_0{
    void *isa;//初始化为&_NSConcreteStackBlock,相当于对象的class_t的结构体
    int flags;
    int reserved;
    void *Funptr;//初始化指向调用函数__main_block_func_0
    struct __main_block_desc_0 *desc;
}

class_t结构体

struct class_t{
    struct class_t *isa;
    struct class_t *superclass;
    Cache cache;//方法缓存
    IMP *vtable;//方式实现
    unitptr_data_NEVER_USE
}

截获自动变量值

截获的自动变量会赋值到Block的成员变量当中,Block中为使用的自动变量不会截获

struct __main_block_impl_0{
    void *isa;
    int flags;
    int reserved;
    void *Funptr;
    struct __main_block_decs_0 *decs;
    //截获的成员变量,构造函数初始化时进行赋值
    int val;
    const char *fmt;
}

Block中不能使用c语言数组类型的变量,这是因为c语言数组类型变量不能赋值给c语言数组类型变量,可以使用指针来解决

void func(char a[10]){
    char b[10] = a;//c语言数组赋值给数组非法
    printf("%d\n",b[0])
}
int main(){
    char a[10] = {2};
    func(a);
}

__block说明符

在block中保存值的第一种方法是使用c语言中的静态变量,全局变量,静态全局变量;第二种则是使用"__block储存域说明符"

__block编译转换的代码:

struct __Block_byref_val_0{
    void *isa;
    __Block_byref_val_0 *__forwarding;//指向自身
    int __flags;
    iny __size;
    int val;//使用值
}

struct __main_block_impl_0{
    struct __block_impl impl;
    struct __main_block_decs_0 *Decs;
    __Block_byref_val_0 *val;//block使用__block结构体
    ...
}

__block变量的赋值代码的转换

^{val = 1};

转为:

static void __main_block_func_0(struct __main_block_impl_0 *__cself){
    __Block_byref_val_0 *val = __cself->val;
    (val->__forwarding->val) = 1;//通过forwarding指针访问使用值(主要为了从栈拷贝到堆中访问同一个内存区域)
}

Block存储域

Block类的存储区域

设置对象的区域
_NSConreteStackBlock
_NSConcreteGlobalBlock 数据区域(.data区)
_NSConcreteMallocBlock

存储在程序的数据区域的情况:

impl.isa = &_NSConcreteGlobalBlock

除此之外存储在栈区;

impl.isa = &_NSConcreteStackBlock

为了解决变量作用域结束时,栈上的block和__block变量被释放的问题,将栈上Block复制到堆上

impl.isa = &_NSConcreteMallocBlock

__block结构体变量中的__forwarding指针可以保证无论在栈上还是堆上,都能正确访问__block变量;

ARC有效时,大多数情形编译器都能够恰当的进行判断,自动的将block从栈上复制到堆上;例如将block作为函数返回值就会自动生成objc_retainBlock(),即_Block_copy;

栈上复制到堆上会消耗CUP资源,如果在栈上能够使用就不必复制,某些情况就需要手动复制;

- (NSArray *)getBlockArray{
    int val = 10;
    //需要执行copy复制到堆上,否则变量作用域结束,会销毁,执行异常
    return [[NSArray alloc] initWithObjects:[^{NSLog(@"blk1--%d",val);} copy], [^{NSLog(@"blk2--%d",val);} copy], nil];
}

    typedef void (^blk_t)(void);
    testObj *obj = [[testObj alloc] init];
    NSArray *arr = [obj getBlockArray];
    blk_t blk = [arr objectAtIndex:1];
    NSLog(@"blk class %@",[blk class]);
    blk();

按不同储存区域copy之后的变化:

设置对象的区域 复制效果
_NSConreteStackBlock 从栈复制到堆上
_NSConcreteGlobalBlock 数据区域(.data区) 什么都不做
_NSConcreteMallocBlock 引用计数增加

ARC有效时多次复制不会有问题

__Block变量存储区域

1.当Block从栈中复制到堆中时,所截获的__block也会从栈中赋值到堆中,并且被Block所持有;当多个Block截获__block变量时,__block引用计数的会增加,符合引用计数的内存管理思考方式.

2.使用__block变量结构体中的__fowarding指针可以使得不管__block变量配置的栈上还是堆上都可以正确访问解释如下:

__block int val = 0;
void (^blk)(void) = [^{++ val;} copy];//堆上__block变量
++ val;//栈上__block变量
blk()

当__block从栈上复制到堆上时,栈上的__forwarding指针会指向堆上的__Block_byref_val_0结构体实例,确保访问同一个__block变量

val->__forwarding->val;//栈上和堆上都是这样访问

截获对象

概要:Block中捕获对象的持有与废弃,对象自动从栈copy复制到堆的情况

    typedef void (^blk_t)(id obj);
    blk_t blk;
    {
        NSMutableArray *array1 = [[NSMutableArray alloc] init];
        blk = [^(id obj){
            [array1 addObject:obj];
            NSLog(@"array count : %ld",[array1 count]);
        } copy];
    }
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    NSLog(@"class:%@",[blk class]);

clang编译之后:

struct __main_block_impl_0{
    struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
    id __strong array;//注意使用了__strong修饰符
}

c语言结构体中是不能使用__strong修饰符的对象类型,但是此处oc运行库可以准确把握Block从栈复制到堆以及从堆中废弃的时机;为此,需要在__main_block_desc_0结构体中增加copy和dispose成员变量,以及赋值的成员变量函数__main_block_copy_0和__main_block_dispose_0;两个函数定义如下:

__Block_object_assign函数相当于retain实例方法,持有对象

static void __main_block_copy_0(struct __main_block_impl_0 *dst,struct __main_block_impl_0 *src){
    __Block_object_assign(&dst->array,src->array,BLOCK_FIELD_IS_OBJECT);//BLOCK_FIELD_IS_OBJECT表示对象;__block类型则是BLOCK_FIELD_IS_BYREF
}

__Block_object_dispose函数相当于release的实例方法,谁都不持有Block时调用

static void __main_block_dispose_0(struct __main_block_impl_0 *dst,){
    __Block_object_dispose(&dst->array,BLOCK_FIELD_IS_OBJECT);
}

这两个函数不会主动调用,从栈copy到堆(本质调用_Block_copy)的时机如下:

__block也是同理,截获的__block从栈复制到堆上时调用copy函数,不再持有__block变量时调用dispose函数,所使用的参数是BLOCK_FIELD_IS_BYREF

__block变量和对象

__block修饰符可以指定任何类型的自动变量,例如指定对象类型

__block id object = [[NSObject alloc] init];

在Block中使用对象类型的的自动变量时,当Block从栈拷贝到堆中时,调用吧_Block_object_assign函数,持有Block捕获的对象;当堆上的Block废弃时,调用_Block_object_dispose函数,释放截获的对象.__block变量也会发生同样的过程,参数不一样BLOCK_FIELD_IS_OBJECT/BLOCK_FIELD_IS_BYREF

使用__weak修饰的对象则不能超出变量作用域存在,因为Block不持有对象,会被释放

Block循环引用

有两种方式:1.使用weak临时变量;2使用__block变量类型,在Block函数中将捕获的变量值为nil

一个循环引用的例子:

typedef void (^blk_t)(void);
@interface testObj : NSObject{
    blk_t blk_;//testObj类持有block
}
@end
@implementation testObj
- (instancetype)init{
    if (self = [super init]) {
        blk_ = ^{NSLog(@"self = %@",self);};//block赋值给成员变量,block从栈拷贝到堆中;self是__strong修饰的id类型,block持有self
    }
    return self;
}

1.第一种方法,使用__weak修饰符的方式
id __weak weakSelf = self;
blk_ = ^{NSLog(@"self = %@",weakSelf);};//Block结构体中捕获的self为__weak类型,不再持有self

1.2.面向iOS4的程序可以使用__unsafe_unretained修饰符.由于在调用block函数时,self必定是存在的,不用担心调用时weakSelf为空,产生悬垂指针的情况

 id __unsafe_unretained weakSelf = self;
 blk_ = ^{NSLog(@"self = %@",weakSelf);};

1.3.Block中使用了成员变量,捕获的也是self

typedef void (^blk_t)(void);
@interface testObj : NSObject{
    blk_t blk_;//testObj类持有block
    id obj_;
}
@end
@implementation testObj
- (instancetype)init{
    if (self = [super init]) {
        blk_ = ^{NSLog(@"obj_ = %@",obj_);};//obj_只是对象的成员变量,捕获的是self
    }
    return self;
}

使用个临时weak变量传到Block中(或者__unsafe_unretained)

id __weak weakObj = obj_;
blk_ = ^{NSLog(@"obj_ = %@",weakObj);}
2.第二种方法,使用__block变量
__block id tmp = obj_;
blk_ = ^{
        NSLog(@"obj_ = %@",tmp);
        tmp = nil;//将对象置位nil,使得__block结构体类型__Block_byref_tmp_0不再持有tmp对象
        }

如果不执行blk_()调用,则会引起内存泄露,Block->__Block变量->tmp对象->Block(成员函数)

使用__block变量的优点:

缺点:

copy/release

1.ARC无效时copy和release;2.ARC有效无效时__block解决循环引用的区别

1.ARC无效时,一般需要手动从栈复制到堆中.
blk_t blk_on_heap = [blk_on_stack copy];
[blk_on_heap release];
2.ARC有效无效时__block解决循环引用的区别

ARC无效时,使用了__Block修饰的对象类型,该对象不会retain,即__block不持有对象;无__Block修饰则会自动retain.在解决循环引用上与ARC有效时有区别:

__block id tmp = self;
blk_ = ^{NSLog("self = %@",tmp)};//不需要tmp置nil,因为ARC无效时__block不持有对象
上一篇下一篇

猜你喜欢

热点阅读