美文网首页
iOS高级编程 --Block

iOS高级编程 --Block

作者: shengchang | 来源:发表于2020-06-19 16:10 被阅读0次

概要

  • 带有自动变量的匿名函数

Blocks模式

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

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

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

Block类型变量

//声明block类型变量
int (^blk)(int)
//函数指针
int(*funcptr)(int) = &func
  • block类型变量与c语言类型变量一样可以作为自动变量、函数参数、全局变量、静态变量、静态全局变量
  • 通过block类型变量调用Block与c语言函数调用没有区别
//使用typedef简化类型变量
typedef int (^blk)(int)

截获自动变量值

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

__block说明符

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

截获的自动变量

  • 在Block表达式中使用截获的自动变量没问题,但是赋值会编译报错,例如NSMutableArray对象可以正常添加元素,但是重新赋值给这个指针就会编译报错
使用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

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

  • 记述在全局变量的地方有Block语法
  • block语法中未使用应截获的对象
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函数的copy方法
  • Block作为函数的返回值
  • 将Block赋值给__strong修饰符的id类型或者赋值给Block类型的成员变量
  • 方法名中含有usingBlock的cocoa框架方法或者GCD中传递的Block

__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变量的优点:

  • 通过__block变量可以控制对象的持有期
  • 在ios4的环境中使用,不必担心悬空指针(unsafe_unretained)
  • 在执行Block时可以动态的决定是否将nil或者其他对象赋值到__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];
  • 推荐使用copy来持有Block,使用retain的话,如果是[blk_on_stack retain]则不起作用;
  • 在c语言中可以使用Block_copy()/Block_release()来替换
2.ARC有效无效时__block解决循环引用的区别

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

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

相关文章

  • Block

    参考:Apple Block 源码Objective-C高级编程 iOS与OS X多线程和内存管理 Block与函...

  • iOS高级编程 --Block

    概要 带有自动变量的匿名函数 Blocks模式 语法(表达式/函数区别于类型变量) ^返回值类型(可省)+参数列表...

  • block编程, __block, __weak, __stro

    参考:block编程iOS __weak和__strong在Block中的使用__block & __weak &...

  • OC中的Block

    OC中的Block是什么 带自动变量值的匿名函数 --《Objective-c高级编程iOS与OSX多线程...

  • ARC环境下Block的内存管理

    对于Block的相关知识,可以看《Objective-C高级编程 iOS与OS X多线程和内存管理》这本书,写得非...

  • iOS Block浅浅析

    前言 Block真的难,笔者静下心来读《Objective-C 高级编程 iOS与OS X多线程和内存管理》,读的...

  • iOS Block用法和实现原理

    原文iOS Block用法和实现原理 《Objective-C高级编程》是一本有趣又难懂的书,全书就讲了引用计数、...

  • Block 深入浅出

    iOS Block用法和实现原理 《Objective-C高级编程》是一本有趣又难懂的书,全书就讲了引用计数、Bl...

  • 关于Block一些记录

    大概两三周前通过学习《Objective-C高级编程 iOS与OS X多线程和内存管理》中的Block章节,系统深...

  • iOS中的Block一

    参考文章 《Objective-C高级编程》Blocks深入研究Block捕获外部变量和__block实现原理谈谈...

网友评论

      本文标题:iOS高级编程 --Block

      本文链接:https://www.haomeiwen.com/subject/hxptrttx.html