Block原理、Block变量截获、Block的三种形式、__block

什么是Block?

是将函数及其执行上下文(环境)封装起来的oc对象。

NSInteger num = 3;

    NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){

    return n*num;    };

    block(2);

block内部有isa指针,所以说其本质也是OC对象。

block内部则为:

static NSInteger __WYTest__blockTest_block_func_0(struct __WYTest__blockTest_block_impl_0 *__cself, NSInteger n) {

  NSInteger num = __cself->num; // bound by copy

        return n*num;

    }

所以说 Block是将函数及其执行上下文封装起来的对象,既然block内部封装了函数,那么它同样也有参数和返回值。

__block_impl结构体为

struct __block_impl {

  void *isa;//isa指针,所以说Block是对象

  int Flags;

  int Reserved;

  void *FuncPtr;//函数指针

};

Block变量截获

1.局部变量截获是值截获

eg:NSInteger num = 3;

        NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){

        return n*num;

    };

    num = 1;

    NSLog(@"%zd",block(2));

这里的输出是6而不是2,原因就是对局部变量num的截获是值截获。

同样,在block里如果修改变量num,也是无效的,甚至编译器会报错。

NSMutableArray * arr = [NSMutableArray arrayWithObjects:@"1",@"2", nil];

    void(^block)(void) = ^{

        NSLog(@"%@",arr);//局部变量

        [arr addObject:@"4"];

    };

    [arr addObject:@"3"];

    arr = nil;

    block();

打印为1,2,3

局部对象变量也是一样,截获的是值,而不是指针,在外部将其置为nil,对block没有影响,而该对象调用方法会影响

2、局部静态变量截获 是指针截获

static NSInteger num = 3;

    NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){

        return n*num;

    };

    num = 1;

    NSLog(@"%zd",block(2));

输出为2,意味着num = 1这里的修改num值是有效的,即是指针截获。

同样,在block里去修改变量m,也是有效的。

3、全局变量,静态全局变量截获:不截获,直接取值。

static NSInteger num3 = 300;

NSInteger num4 = 3000;

- (void)blockTest{

    NSInteger num = 30;

    static NSInteger num2 = 3;

    __block NSInteger num5 = 30000;

    void(^block)(void) = ^{

        NSLog(@"%zd",num);//局部变量

        NSLog(@"%zd",num2);//静态变量

        NSLog(@"%zd",num3);//全局变量

        NSLog(@"%zd",num4);//全局静态变量

        NSLog(@"%zd",num5);//__block修饰变量

    };    block();

}

编译后

  struct __WYTest__blockTest_block_impl_0 {

  struct __block_impl impl;

  struct __WYTest__blockTest_block_desc_0* Desc;

   NSInteger num;//局部变量

   NSInteger *num2;//静态变量

  __Block_byref_num5_0 *num5; // by ref//__block修饰变量

  __WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, NSInteger *_num2, __Block_byref_num5_0 *_num5, int flags=0) : num(_num), num2(_num2), num5(_num5->__forwarding) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

};

( impl.isa = &_NSConcreteStackBlock;这里注意到这一句,即说明该block是栈block)

可以看到局部变量被编译成值形式,而静态变量被编成指针形式,全局变量并未截获。而__block修饰的变量也是以指针形式截获的,并且生成了一个新的结构体对象:

struct __Block_byref_num5_0 {

 void *__isa;

 __Block_byref_num5_0 *__forwarding;

 int __flags;

 int __size;

 NSInteger num5;

 };

该对象有个属性:num5,即我们用__block修饰的变量。

这里__forwarding是指向自身的(栈block)。

一般情况下,如果我们要对block截获的局部变量进行赋值操作需添加__block修饰符,而对全局变量,静态变量是不需要添加__block修饰符的

另外,block里访问self或成员变量都会去截获self。


Block的几种形式

分为全局Block(_NSConcreteGlobalBlock)、栈Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三种形式

其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区

1、不使用外部变量的block是全局block

eg:NSLog(@"%@",[^{

        NSLog(@"globalBlock");

    } class]);

输出:__NSGlobalBlock__

2、使用外部变量并且未进行copy操作的block是栈block

eg: NSInteger num = 10;

    NSLog(@"%@",[^{

        NSLog(@"stackBlock:%zd",num);

    } class]);

输出:__NSStackBlock__

日常开发常用于这种情况

[self testWithBlock:^{

    NSLog(@"%@",self);

}];

- (void)testWithBlock:(dispatch_block_t)block {

    block();

    NSLog(@"%@",[block class]);

}

3、对栈block进行copy操作,就是堆block,而对全局block进行copy,仍是全局block

比如堆1中的全局进行copy操作,即赋值:

void (^globalBlock)(void) = ^{

        NSLog(@"globalBlock");

    };

NSLog(@"%@",[globalBlock class]);

输出:__NSGlobalBlock__

而对2中的栈block进行赋值操作:

NSInteger num = 10;

void (^mallocBlock)(void) = ^{

        NSLog(@"stackBlock:%zd",num);

    };

NSLog(@"%@",[mallocBlock class]);

输出:__NSMallocBlock__

对栈blockcopy之后,并不代表着栈block就消失了,左边的mallock是堆block,右边被copy的仍是栈block

[self testWithBlock:^{

    NSLog(@"%@",self);

}];

- (void)testWithBlock:(dispatch_block_t)block{

    block();

    dispatch_block_t tempBlock = block;

    NSLog(@"%@,%@",[block class],[tempBlock class]);

}

输出:__NSStackBlock__,__NSMallocBlock__

即如果对栈Block进行copy,将会copy到堆区,对堆Block进行copy,将会增加引用计数,对全局Block进行copy,因为是已经初始化的,所以什么也不做。

另外,__block变量在copy时,由于__forwarding的存在,栈上的__forwarding指针会指向堆上的__forwarding变量,而堆上的__forwarding指针指向其自身,所以,如果对__block的修改,实际上是在修改堆上的__block变量。

即__forwarding指针存在的意义就是,无论在任何内存位置, 都可以顺利地访问同一个__block变量。

另外由于block捕获的__block修饰的变量会去持有变量,那么如果用__block修饰self,且self持有block,并且block内部使用到__block修饰的self时,就会造成多循环引用,即self持有block,block 持有__block变量,而__block变量持有self,造成内存泄漏。

比如: 

 __block typeof(self) weakSelf = self;

    _testBlock = ^{

        NSLog(@"%@",weakSelf);

    };

    _testBlock();

如果要解决这种循环引用,可以主动断开__block变量对self的持有,即在block内部使用完weakself后,将其置为nil,但这种方式有个问题,如果block一直不被调用,那么循环引用将一直存在。

所以,我们最好还是用__weak来修饰self

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