iOS-深入研究Block

前言

在我们的实际开发中,Block的使用率相当之高,我们在使用Block的时候,会遇到各种各样的问题,比如经典的循环引用,那么这些问题到底是怎么产生的,我们又该如何去解决,这就需要我们对Block有深入的了解,才能更好的解决这些问题,今天我们就来深入分析一下Block。

1 Block的类型

我们知道Block分为全局Block栈Block堆Block。我们怎么区分或者判断是哪种Block呢,我们来分析下。

 int a = 10;
    void (^block)(void) = ^{
        //NSLog(@"robert - %d",a);
    };

这是一个全局的Block,我们看下效果,如图:


1

我们把NSLog这行代码打开,再次运行,看下效果


2

这是一个堆Block。
在ARC下,我们怎么打印出栈Block呢,我们把代码改成这样
 int a = 10;
   void (__weak ^block)(void) = ^{
        NSLog(@"robert - %d",a);
    };

再次运行,如图所示

3

这是一个栈Block。
Block类型划分,具体点,我们该怎么区分,如下所示,

GlobalBlock

  • 位于全局区。
  • 在Block内部不使用外部变量,或者只使用静态和全局变量。

MallocBlock

  • 位于堆区。
  • 在Block内部使用局部变量或者OC属性,并且赋值给强引用或者Copy修饰的变量。

StackBlock

  • 位于栈区。
  • 与MallocBlock一样,可以在内部使用局部变量或者属性,但是不能赋值给强引用或者Copy修饰的变量。

全局Block比较好区分,MallocBlock和StackBlock不易区分, 下面我们来看这样一段代码

- (void)blockRetaincount{
    NSObject *objc = [NSObject new];
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    void(^strongBlock)(void) = ^{
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    strongBlock();

    void(^__weak weakBlock)(void) = ^{ // + 1
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    weakBlock();
    
    void(^mallocBlock)(void) = [weakBlock copy];
    mallocBlock();
}

请问这里的打印是多少?
我们运行一下,看下效果,如图


4

为什么结果是这样呢的?需要我们具体分析下。

  • *NSObject objc = [NSObject new];这里之后obj的引用计数为1

  • void(^strongBlock)(void) = ^{
    NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    }; 这是为什么是3呢,因为strongBlock外部捕获了objc,进行了copy操作,引用计数+1,strongBlock是一个MallockBlock,会进行强持有,从栈上拷贝堆上,这个时候堆上的对象引用计数再次+1,就变成了3。

  • void(^__weak weakBlock)(void) = ^{ // + 1
    NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    }; 这是一个栈Block,因为weakBlock外部捕获了objc,进行了copy操作,引用计数+1 ,但是它是一个栈Block,不会从栈上拷贝堆上,所以这里打印4。

  • void(^mallocBlock)(void) = [weakBlock copy]; 这里对weakBlock进行一次copy操作,从栈上拷贝到堆上,这个时候堆上的对象引用计数+1,所以这里打印5。

我们接着再看一段代码,如下所示

- (void)blockCopy{
    int a = 0;
    void(^ __weak weakBlock)(void) = ^{
        NSLog(@"-----%d", a);
    };
    struct _RoBlock *blc = (__bridge struct _RoBlock *)weakBlock;
    id __strong strongBlock = weakBlock;
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;
    strongBlock1();
}

这段代码的执行结果是什么?
我们来运行下,看下效果,如图

5

这里闪退了,我们来分析下。
blc->invoke这里的invoke进行调用执行。

struct _RoBlock *blc = (__bridge struct _RoBlock *)weakBlock; 这里把weakBlock进行强转。
id __strong strongBlock = [weakBlock copy]; 这里把weakBlock进行copy操作。
blc->invoke = nil; 把blc的invko置空。
void(^strongBlock1)(void) = strongBlock;这段代码只是强转一次,把strongBlock具有block的特性,可以调用执行。
说明weakBlock强转成blc时,他们还是指向同一块内存区域,blc的invoke为nil,那么weakBlock的invoke也是nil,所以这里调用的时候闪退。

如何来解决呢,我们可以进行copy操作,id __strong strongBlock = weakBlock; 改成
id __strong strongBlock = [weakBlock copy]; 我们再试下,如图

6

这样就就正常了,一定要在blc->invoke = nil;之前进行copy操作。

我们再分析一段这代码,如下所示

- (void)blockDemo{
//    NSObject *a = [NSObject alloc];
    int a = 0;
    void(^__weak weakBlock)(void) = nil;
    {
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"strongBlock ---%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"1");
    }
    weakBlock();
}

请问这段代码的执行结果是什么?
我们来运行下,看下效果,如图


6

我们分析下它的执行流程是什么。

  • void(^__weak weakBlock)(void) = nil;这是一个栈Block
  • void(^__weak weakBlock)(void) = nil;
    {
    void(^__weak strongBlock)(void) = ^{
    NSLog(@"strongBlock ---%d", a);
    };
    weakBlock = strongBlock;
    NSLog(@"1");
    }
    weakBlock();
    }这里一个代码块,跟void(^__weak weakBlock)(void) = nil;无关
  • 在代码块中strongBlock是一个栈Block
  • 把strongBlock给了weakBlock, weakBlock也是一个栈Block
  • 栈Block的生命周期是在blockDemo这个方法中,也就是说只有出了这个方法,这个栈Block的生命周期才会结束,所以这里运行是正常的。

我们改下代码,反* void(^__weak strongBlock)(void) = ^{改成 * void(^strongBlock)(void) = ^{ ,看下运行效果,如图

7

这里闪退了。
原因分析Block默认是强引用的,这里是在堆区,它的生命周生期是在代码块中,出了代码块,它就会被锁毁,weakBlock = strongBlock;赋值后,weakBlock也是在堆区,所以这里调用weakBlock()就会闪退。
我把weakBlock = strongBlock;改成weakBlock = [strongBlock copy];同样会闪退,原因同上,因为也是在堆区,出了代码块,销毁了。

Block循环引用的分析

8

我们看下这张图,
A对于进行B持有, A会对B进行引用计数+1的操作,当B要想释放掉的,需要等待A发送realses信号,如果B的引用计数为0的时候,dealloc就会被调用,只有当A在dealloc的时候会发送信号给B。
9

这张图描述了,A持有B,B也同时持有了A,这个时候就构成了无法释放的循环,因为B的释掉依赖A的释放,A的释放又依赖的B的释放,产生了循环引用。

我们先来段代码,案例1

- (void)test1 {
    self.name = @"robert";
    self.block = ^(void) {
        NSLog(@"%@", self.name);
    };
    self.block();
}

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

我们运行一下,看下dealloc是否调用了,如图

10

这里没有打印,说明这里产生循环引用,
这段代码,案例2

[UIView animateWithDuration:1 animations:^{
        NSLog(@"%@", self.name);
    }];

运行效果,如图


1

这里可以看出调用了dealloc,所以没有产生循环引用,这是为什么,我们分析下。

案例1中,self->block->self 产生循环引用
__weak typedef(self) weakSelf = self;使用weak指针来解决
我们再改一下代码,如下

- (void)test1 {
    __weak typeof(self)weakSelf = self;
  self.name = @"robert";
  self.block = ^(void) {
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)3*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
          NSLog(@"name=%@", weakSelf.name);
      });
  };
  self.block();
}

看下它的执行效果,如图

11

name的值打印出来了空,这是为什么呢,我们只不过延迟几秒种调用而已,发生了什么事情?
分析,当我们返回上个页面时,self被释放掉了,weakSelf也被释放掉了,这个时候再调用就是nil。
我们可以通过__strong __typeof(weakSelf)strongSelf = self;来解决,如代码所示

- (void)test1 {
    __weak typeof(self)weakSelf = self;
    self.name = @"robert";
    self.block = ^(void) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)3*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            NSLog(@"name=%@", strongSelf.name);
        });
    };
    self.block();
}

运行结果如下

12

这里运行就正常了。
也就是说通过weak-strong-dance的方法解决。
strongSelf虽然是强引用,是个临时变量,它的生命周期是在block的范围内,会自动释放。
我们再来讲第二种方示,代码如下:

 __block SecondViewController *vc = self;
    self.name = @"robert";
    self.block = ^(void) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)3*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            NSLog(@"name=%@", vc.name);
            vc = nil;
        });
    };
    self.block();
}

运行结果,如图


13

vc被self.block持有了,但是我们把vc置空,断开了循环。
self->block->vc->self,vc=nil断开了循环链。
对外部变量操作加上__block的才能修改,后续会详细讲。
当然我们还可以通过传参的方式来解决。

我们再来看一段代码,如下所示:

static SecondViewController *staticSelf_;
- (void)blockWeakStatic {
    __weak typeof(self) weakSelf = self;
    staticSelf_ = weakSelf;
}
- (void)dealloc{
    NSLog(@"dealloc走你");
}

请问这里运行效果是什么,会不会产生循环引用?
分析结果:这里肯定会产生循环引用,weakSelf->self,staticSelf->weakSelf,staticSelf是个全局静态变量,不会自动释放,所以这里产生了循环引用。
我们来运行下,如图

14

这里发现dealloc没有调用,所以产生循环引用了。
这里的weakSelf其实就是Self,我们断点打印一下,如图
15

这里可以看出,selfweakSelf、"staticSelf_"是指向同一块内存区域,也就是说weakSelfself是映射关系,他们三个又构成了循环链,所以就产生了循环引用。

我们接着再分析一代码,如下

- (void)block_weak_strong {
    __weak typeof(self) weakSelf = self;
    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf;
        weakSelf.doStudent = ^{
            NSLog(@"111111111----- %@", strongSelf);
        };
       weakSelf.doStudent();
    };
   self.doWork();
}

请问这段代码的是否会产生循环引用?
答案是肯定会产生循环引用。
我们运行一,先看下结果,如图


16

上图已经说明了问题,会产生循环引用,究竟是为什么,我们来看下。

  • self.doWork对self持有
  • __strong typeof(self) strongSelf = weakSelf;
    weakSelf.doStudent = ^{
    NSLog(@"111111111----- %@", strongSelf);
    };
    weakSelf.doStudent();这里strongSelf对weakSelf持有,strongSelf虽然是临时变量,但是它的生命周期是在doWork这个block中,在strongSelf释放前,会执行上述代码,doStudent是会对strongSelf进行持有,捕获进去,对strongSelf引用计数+1操作,也就是说strongSelf的引用计数是2了,虽然在doWork这个block结束时会-1操作,但是还是无法释放这个strongSelf
  • 我们可以在NSLog(@"111111111----- %@", strongSelf);代码下面加入strongSelf=nil,断开循环链来解决。

结语

这篇文章结合实际应用讲述的Block的类型,循环引用的产生,以及如何解决这些问题,如有疑问,大家可以相互交流学习。

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

推荐阅读更多精彩内容