28.iOS底层学习之block初学习

本章提纲
1、Block的几种类型
2、Block常见试题
3、Block的循环引用问题

1、Block的几种类型

Block分为三种类型,分别是GlobalBlockMallocBlockStackBlock

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

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

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

1.1 GlobalBlock类型实例

全局Block类型,不使用局部变量,或者内部使用的变量是全局的。

Block内部无操作
 void (^block)(void) = ^{
    };
    NSLog(@"%@",block);

打印结果:<__NSGlobalBlock__: 0x100b72100>

Block内部引用静态变量
static int jingtai = 3;
void (^block)(void) = ^{
        NSLog(@"%d",jingtai);
    };
    NSLog(@"%@",block);

打印结果:<__NSGlobalBlock__: 0x10430d100>

1.2 MallocBlock类型实例

Block,内部使用局部变量或者OC属性,并且赋值给强引用Block或者Copy变量。

使用局部变量并且强引用
 int a = 2;
    void (^block)(void) = ^{
        NSLog(@"%d",a);
    };
    NSLog(@"%@",block);

打印结果:<__NSMallocBlock__: 0x600001dc8b40>
block,默认是强引用。

Copy变量的情况
int a = 10;
    void (__weak ^block)(void) = ^{
        NSLog(@"%d",a);
    };
    NSLog(@"%@",[block copy]);

打印结果:<__NSMallocBlock__: 0x600002d307e0>
如果我不进行[block copy]的操作,直接打印这个block的话,是一个栈Block,经过copy,打印就是堆Block。

1.2 StackBlock类型实例

Block和堆Block很相似,区别在于赋值的对象是强引用还是弱引用。还是上边那个例子🌰,如果我进行Copy操作,打印就是一个stackBlock

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

打印结果:<__NSStackBlock__: 0x30d7e5548>

2、Block常见试题

  • Block的引用计数考题
    下面代码输出的结果是。
- (void)blockDemo{
    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();
}

输出结果是:1,3,4,5
解析
1、首先是创建一次,引用计数+1,所以第一次打印是引用计数是1。
2、然后来到这个strongBlock关键的地方,它是一个堆Block,strongBlock本身引用了一次 底层实际执行了copy操作,所以又引用一次 +2,此时引用计数变成3。
3、然后后面来到weakBlock这里,它是弱引用,是个栈Block,底层不会拷贝 所以+1,此时引用计数变成了4。
4、最后mallocBlock是由weakBlock手动copy一次得到的,所以再加1,引用计数为5。

截图
  • Block的浅拷贝
    下面代码运行结果
- (void)blockDemo1{
    
    int a = 0;
    void(^__weak weakBlock)(void) = ^{
        NSLog(@"a:%d", a);
    };
    
    struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;

    id __strong strongBlock = weakBlock;
    NSLog(@"weakBlock:%@",weakBlock);
    NSLog(@"strongBlock:%@",strongBlock);
 
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;
    NSLog(@"strongBlock1:%@",strongBlock1);
    
    strongBlock1();
}

运行结果程序crash
解析weakBlock是一个栈类型的Block,blc的初始化是直接把自己的指针指向了这个weakBlock,他们指向的是同一块内存空间。然后后面的strongBlock赋值和blc的情况是相似的,也是指针指向了那块内存,所以
blc->invoke置为空相当于把那块内存值为空了,所以此刻不管是blc的指向还是weakBlock的指向,或者是strongBlock的指向,他们都指向了一块nil空间,是不可以再去访问的,所以会崩溃了(EXC_BAD_ACCESS)。

上述代码修复

- (void)blockDemo1{
    int a = 0;
    void(^ __weak weakBlock)(void) = ^{
        NSLog(@"-----%d", a);
    };
    struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;

    id __strong strongBlock = [weakBlock copy];
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;
    strongBlock1();
}

最后是调用strongBlock1,所以strongBlock1赋值时可以进行copy,让它在堆空间上,开辟了另外一块空间,原来的那块置为nil不会对新的空间有影响。

  • Block的堆栈释放差异
    下面代码的输出是什么:
- (void)blockDemo3{
    int a = 8;
    void(^__weak weakBlock)(void) = nil;
    {
        // 栈区
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"---%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
    }
    weakBlock();
}

运行结果1 - <__NSStackBlock__: 0x308640520> - <__NSStackBlock__: 0x308640520>
---8

解析

  • weakBlock是一个栈区的Block但是初始化为nil,一开始是空。
  • 然后下面的{}是一个代码块,这个主要影响部分代码的作用域。
  • strongBlock是栈区的Block,初始化正常。
  • 然后weakBlock = strongBlock;weakBlock指向了strongBlock指针指向的的内存。
  • 所以后面打印weakBlock和strongBlock他们的地址是一样的0x308640520
  • 然后出作用域,调用weakBlock(),因为还有指针weakBlock指向0x308640520,但是它是个弱引用,strongBlock出了整个函数才会被释放,所以后面weakBlock()还可以调用代码块,调用的时候代码块没有被释放。

如果上述代码做简单的修改,把strongBlock前边的__weak去掉:

- (void)blockDemo3{
    int a = 8;
    void(^__weak weakBlock)(void) = nil;
    {
        // 堆
        void(^strongBlock)(void) = ^{
            NSLog(@"---%d", a);
        };
        weakBlock = strongBlock;
        
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
    }

    weakBlock();
}

那么运行就会崩溃了。原因就是,堆空间,出了第一个花括号的作用域,strongBlock就被释放掉了,而weakBlock是弱持有,不是强持有,所以就调用不到块了。花括号去掉就没问题了。截图看下花括号对比前后的结果。


去掉花括号之前
去掉花括号之后

通过血与泪的教育😭😭😭,这道题告诉我们了一个很重要的人生哲理!就是
花括号不要乱写!!!!!!


3、Block的循环引用问题

正常情况下,一个对象A如果强持有对象B,那么B的引用计数retainCount会进行+1的操作,当A对象释放时会发送delloc给B,此时B的引用计数retainCount会进行-1,当B的引用计数为0的时候,B也会被释放掉。

但是,总有那么些特殊的情况,A已经持有了B的时候,B非得又持有了A,他们相互持有了,都等着对方先放手,此时就形成了一个环,不靠外力是断不开的,这种情况,就发生了循环引用

3.1 循环引用的形成

下面来看一段经典的循环引用

  • 经典循环引用示例
- (void)test1{
    self.name = @"ABC";
    self.block = ^{
        NSLog(@"%@",self.name);
    };
}
  • 上述示例self->block ,block->self,相互持有,形成了环,delloc不走。
3.2 解决循环引用

通常我们解决循环引用的方式有三种

  • 进行_ weak弱引用,有时候要和 _strong配合使用
  • 强制断链,将其中一个置为nil。
  • 将持有者改为Block的参数进行传递使用。
3.2.1 _ _weak弱引用

上述代码,我们进行简单的改造,就可以解决循环引用的问题,通过用_ _weak修饰的方式。


image.png

有时候在block的内部会有需要进行异步线程的操作,为了避免使用的对象提前被释放,通常会在内部添加_ _strong来增加一次引用计数。

3.2.2 强制断链

强制断链就是在使用完对象后,强制设置为空。如下:


强制断链

但是也会有问题,这种情况必须block被调用,代码块中的代码被执行才会解决循环引用,容易引发bug,如果不调用block中的内容循环引用还是存在的。

Block未被调用的情况

所以这样dealloc是没有走的。

3.2.3作为参数进行传递

我们把上述的vc作为参数传递到block中再进行使用也是可以解决这个问题的。

参数传递

可以看到作为参数传进来是没有问题,参数就避免了Block对外部变量的捕获的问题。


这篇文章对Block日常的一些使用情况做了一个简单概括,下一篇会深入研究Block的结构,底层原理等。

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

推荐阅读更多精彩内容