Blocks笔记

block是什么? block本质其实是能够截获自动变量的匿名函数,是一个oc对象(结构体),结构体里面的isa指针指向自己的类,但是它和对象又有一定的区别. 对象一般分配在堆上,而block默认分配在栈上(其他情况下面会说明),Block是C的扩展,指向结构体__main_block_impl_0的指针,创建__main_block_impl_0结构体时会根据变量环境初始化结构体成员变量,由此来分配内存区域,截获变量,管理内存,以及一些copy操作.

block的结构

通过clang工具翻译oc代码可以看到block的结构体信息,具体含义见注释

#import "test.h"

@implementation test
//oc代码
- (instancetype)init {
    if (self == [super init]) {
        void(^testBlock)() = ^{
            NSLog(@"hello world!");
        };
        testBlock();
    }
    return self;
}

@end
clang -rewrite-objc test.m
//clang翻译之后的截取代码
// @implementation test

//block指向__test__init_block_impl_0此结构体,block的入口
struct __test__init_block_impl_0 {
  struct __block_impl impl; //见下面
  struct __test__init_block_desc_0* Desc; //描述block信息的结构体
 //结构体的构造函数,含有初始化参数
   __test__init_block_impl_0(void *fp, struct   __test__init_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock; //block 创建在栈区,因此父类为_NSConcreteStackBlock
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
   }
};

//block实现的结构体
struct __block_impl {
  void *isa; //指向所属的类
  int Flags; //按位承载 block 的附加信息;
  int Reserved; //保留变量
  void *FuncPtr; //函数指针,指向block执行的函数
};

//block要执行的函数代码
static void __test__init_block_func_0(struct __test__init_block_impl_0 *__cself) {
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_gd_3sky2l_d5jv0t9ww_t8_w3680000gp_T_test_87f55d_mi_0);
}

//描述block信息结构体
static struct __test__init_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} 

//结构体变量
__test__init_block_desc_0_DATA = { 0, sizeof(struct __test__init_block_impl_0)};

// @end
block截获自动变量值
int number = 1;
void(^block)() = ^{
     NSLog(@"number = %d",number);
};
number++;
block();

上面结果是1,原因就是block截获了自动变量值;到底是如何截获的呢?
根据block结构分析(见注释):

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int number; //形成同名成员变量
    //通过下面的构造函数中的numberValue(_number)把变量值保存到结构体中的同名成员变量number中
     __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, int flags=0) : numberValue(_number)
    {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    //取出同名的成员变量值
    int number = __cself->number; // bound by copy
    NSLog(@"number = %d",number);
}

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

截获的变量只能使用不能修改,要想修改得加上__block;

//block截获的变量可以使用不能修改
- (IBAction)test_5:(id)sender {
    NSMutableArray *mutArray = [[NSMutableArray alloc]init];
    // 此block在栈上,不会对对象的引用计数产生影响
    void(^block)() = ^{
#if 1
        [mutArray addObject:@"hello - 1"];
        [mutArray addObject:@"hello - 2"];
        [mutArray addObject:@"hello - 3"];
#else
        //被修改会报错,修改要在变量前加__block修饰
        NSMutableArray *array = [[NSMutableArray alloc]initWithObjects:@"hello", nil];
        mutArray = array;
#endif
    };
    block();
    NSLog(@"count:%ld\n firstObject: %@",mutArray.count,mutArray.firstObject);
}
打印:
  count:3
  firstObject: hello - 1
block的存储域

根据Block在内存中的位置分为三种类型_NSConcreteGlobalBlock,_NSConcreteStackBlock, _NSConcreteMallocBlock。

  • _NSConcreteGlobalBlock:类似函数,位于text段;
  • _NSConcreteStackBlock:位于栈内存,作用域结束后后Block将无效;
  • _NSConcreteMallocBlock:位于堆内存。

1、当 block 写在全局作用域时,即为 global block;
2、当 block 表达式不引用任何外部变量时,即为 global block;

在以上两种情况下生成的block为_NSConcreteGlobalBlock,其余均为_NSConcreteStackBlock,当 block 从栈拷贝到堆后,当栈上变量作用域结束时,仍然可以继续使用 block,因为此时的block位于堆上,block的类型为:_NSConcreteMallocBlock;所以会将 _NSConcreteMallocBlock 写入 isa如:

impl.isa = &_NSConcreteMallocBlock;
  • 栈上的block:
pragma mark - 栈上的block
 -(IBAction)test_2:(id)sender {
    NSArray *array = [self getBlockArrayFromStack];
    //    [self test];
    void(^block)(void);
    block = array[2];
    block();
}

 -(void)test {
    NSLog(@"hello world!");
}
 
  - (NSArray *)getBlockArrayFromStack {
    NSArray *blockArray = [[NSArray alloc]initWithObjects:  ^{NSLog(@"block-1");},
                                                            ^{NSLog(@"block-2");},
                                                            ^{NSLog(@"block-3");},nil];
    return blockArray;
}

上面代码中注释的[self test]看似对程序无影响,但是打开注释就会导致程序崩溃,关闭则无能正确执行;原因在于:一个方法调用的栈帧没有被新的数据覆盖,仍然保留原来block数据的原因所致。这样显然是不安全的,是不能保证block数据可用的。

  • 堆上的block
    上述代码若是把数组中的block copy到堆上,即把getBlockArrayFromStack替换为下面方法,那么注释代码打开与否都无影响,原因在于copy到堆上后block仍能脱离作用域继续存在,并指向堆上的block;
-(NSArray *)getBlockArrayFromHeap {
    NSArray *blockArray = [[NSArray alloc]initWithObjects:[^{NSLog(@"block-1");} copy],
                           [^{NSLog(@"block-2");} copy],
                           [^{NSLog(@"block-3");} copy],nil];
    return blockArray;
}
__block变量

__block 说明符可以修饰任何类型的自动变量, __block修饰的变量其实也是block是一种结构体,初始化结构体的时候会把变量赋值给结构体的同名成员变量,若是变量是对象则会持有它,访问变量的时候则通过它的成员变量__forwarding来访问,结构如下:

struct __Block_byref_obj_0 
{
    void *__isa;
    __Block_byref_obj_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*); 
    __strong id obj;
};

static void __Block_byref_id_object_copy_131(void *dst, void *src) 
{
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

static void __Block_byref_id_object_dispose_131(void *src) 
{
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

/* __block variable declaration */

__Block_byref_obj_0 obj = { 0,
                            &obj,
                            0x2000000, 
                            sizeof(__Block_byref_obj_0), 
                            __Block_byref_id_object_copy_131, 
                            __Block_byref_id_object_dispose_131,
                            [[NSObject alloc] init]
                           };
  • 当block从栈copy到堆时,__block变量也会一同拷贝到堆上并被该block持有;若干个block引用同一个 __block变量,会增加该block的引用计数,当block废弃时,_block会被释放,若它引用计数为0了就会被废弃,同oc的引用计数内存管理完全一样;

  • 无论是在block语法中,block语法外使用__block变量,还是__block变量配置在栈上或堆上,都可以顺利访问到同一个__block变量,访问形式均可转换为: ++(val.__forwarding->val),原因是: 栈上的__block用结构体实例在__block变量复制到堆上,都会将结构体(__block)的成员变量__forwarding的值替换为复制到堆上__block变量用结构体实例的地址,具体见下面例子:

 - (IBAction)test_1:(id)sender {
    __block int val = 0;
    void (^block)(void) = [^{val++;} copy];
    val++;
    block();
    NSLog(@"%d",val);
}
打印结果: 2
block的内存管理
  • block截获对象
typedef void (^myBlock)(id obj);
myBlock block;
- (IBAction)test_6:(id)sender {
    [self captureObject];
    block([[NSObject alloc]init]);
    block([[NSObject alloc]init]);
    block([[NSObject alloc]init]);
}
//截获对象
- (void)captureObject {
    id array = [[NSMutableArray alloc] init];
    block = [^(id obj){
        [array addObject:obj];
        NSLog(@"array count = %ld", [array count]);
    } copy];
}

翻译代码:

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
    id __strong array;

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id __strong _array, int flags=0) : array(_array) {
        impl.isa = &_NSConcreteStackBlock; 
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
    id __strong array = __cself->array;
    [array addObject:obj];
    NSLog(@"array count = %ld", [array count]);
}

static void __main_block_copy_0(struct __main_block_impl_0 *dst, __main_block_impl_0 *src) {
    _Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT);
}

static void __main_block_dispose_0(struct __main_block_impl_0 *src) {
    _Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
}

struct static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long 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 literal and executing the Block */
blk_t blk; 
{
    id __strong array = [[NSMutableArray alloc] init];
    blk = &__main_block_impl_0(__main_block_func_0, 
                               &__main_block_desc_0_DATA, 
                               array, 
                               0x22000000);
    blk = [blk copy];
}
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]); 
  • 总结block的内存管理:
    1.栈上的block copy到堆上时,通过copy方法,__main_block_copy_0函数使用_Block_object_assign函数将变量array对象赋值给block结构体成员变量并持有该对象; _Block_object_assign调用相当于retain实例方法的函数,而__main_block_dispose_0函数使用_Block_object_dispose函数释放赋值在结构体成员变量array中的对象;
    以下情形,block会从栈copy到堆:
  1. .当 block 调用 copy 方法时,如果 block 在栈上,会被拷贝到堆上;
    2).当 block 作为函数返回值返回时,编译器自动将 block 作为 _Block_copy 函数,效果等同于 block 直接调用 copy 方法;
    3).当 block 被赋值给 __strong id 类型的对象或 block 的成员变量时,编译器自动将 block 作为 _Block_copy 函数,效果等同于 block 直接调用 copy 方法(block用copy修饰的原因);
    4).当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。这些方法会在内部对传递进来的 block 调用 copy 或 _Block_copy 进行拷贝;

2.栈区的block(_NSConcreteStackBlock)在作用域结束后就会被废弃;
3.全局的block(_NSConcreteGlobalBlock)的实例是在数据区域,全局都能访问到,不依赖执行时的状态,因此retain、copy、release操作都无效;

block的循环引用

block循环引用的例子:
例1:

typedef void (^blk_t)(void);

@interface MyObject : NSObject {
    blk_t blk_;
} 
@end

@implementation MyObject

- (id)init {
    self = [super init];
    blk_ = ^{NSLog(@"self = %@", self);}; 

    return self;
}

- (void)dealloc {
    NSLog(@"dealloc");
} 
@end

例2:

@interface MyObject : NSObject {
    blk_t blk_;
    id obj_; 
}
@end

@implementation MyObject 

- (id)init {
    self = [super init];
    blk_ = ^{ NSLog(@"obj_ = %@", obj_); }; 
    return self;
}
...
...

@end

分析:

  • 两个例子中都是block语法赋值给了成员变量中,因此block语法生成的block从栈复制到堆,例1持有使用的self,例2持有成员变量间接持有self;因此self持有block,block持有self,这就形成了循环引用;

  • 解决循环引用可以通过三种方式:__weak,__unsafe_unretained以及__block来修饰; 因此例1中加入id __weak tmp = self; 例2中加入:id __weak obj = _obj;并替换block中的self 和成员变量_obj即可;
    __block修饰避免循环引用的前提是block必须要执行,并且要在block 内将对象置为 nil ,否则还是会造成循环引用,因此可以通过 __block 变量去控制对象的生命周期;

typedef void (^blk_t)(void);

@interface MyObject : NSObject {
    blk_t  blk_;
} 
@end

@implementation MyObject 

- (id)init {
    self = [super init]; 

    __block id tmp = self;
    blk_ = ^{ 
                NSLog(@"self = %@", tmp);
                tmp = nil; 
            };

    return self;
}

- (void)execBlock {
    blk_();
}

- (void)dealloc {
    NSLog(@"dealloc");
} 

@end
  • 三种方式解决循环引用的原因:__weak__unsafe_unretained修饰变量并不会持有对象,在不支持__weak的情况下可以使用__unsafe_unretained来修饰避免循环引用,而__block在 MRC 下,使用__block说明符也可以避免循环引用。因为当 block 从栈拷贝到堆时,__block对象类型的变量不会被 retain,没有 __block 说明符的对象类型的变量则会被 retian。正是由于 __block 在 ARC 和 MRC 下的巨大差异,我们在写代码时一定要区分清楚到底是 ARC 还是 MRC,详细分析可以参考这里;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容

  • 2.1 Blcoks概要 2.1.1 什么是Blocks Blocks是C语言的扩充功能——“带有自动变量(即局部...
    SkyMing一C阅读 2,315评论 6 18
  • 《Objective-C高级编程》这本书就讲了三个东西:自动引用计数、block、GCD,偏向于从原理上对这些内容...
    WeiHing阅读 9,788评论 10 69
  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 905评论 1 3
  • 花了一段时间对Block深入的研究了一下,以下是我边研究边写的笔记记录,其中大部分内容都是从多线程和内存管理那本书...
    doudo阅读 149评论 0 0
  • 好多天没有写点什么了,找点理由就是最近很忙,坦白点就是不够坚持且肚子里没货。 真心佩服那些写出高质量原创且坚持日更...
    我馨飞翔阅读 316评论 0 0