iOS高级编程 --Block

概要

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

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不持有对象
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352