Blocks官方文档阅读记录 -- Blocks Programming Topics

block.png

前言

最近,我的前同事问了我一个问题,你知道(global、malloc、stack)block吗,什么是(global、malloc、stack)block,相信你在面试中或多或少遇到过一些Blocks的题目,一些简单,一些偏门,但是我认为脱离实际开发的面试题对于面试应聘者是完全无意义的,或许你知道Blocks的内存结构,你也知道Blocksisa在不同情况下指向的类,但这些仅适合拿来茶余饭后交谈的话题(装B)罢了,因为你在半年一年之后自己都不清楚它是什么,只是再翻出来能很快顿悟,所以我不建议面试官将这些放在台面上讨论。
那么这篇文章写的将是实际开发所需要用到的,同时也是Apple官方给出的Blocks文档

关于Block对象的内存结构我不打算在本文讲解,你可以在Block实现的源码中去了解,但是从源码中可以论证一点 Blockisa指针指向的类包含了一下几种:

_NSConcreteStackBlock 
_NSConcreteMallocBlock
_NSConcreteAutoBlock
_NSConcreteFinalizingBlock 
_NSConcreteGlobalBlock
_NSConcreteWeakBlockVariable

并不是很多博客上的文章及面试官口中所说的这三种:

_NSConcreteStackBlock 
_NSConcreteMallocBlock
_NSConcreteGlobalBlock

本文目录

一.Blocks概念、概述
二.Blocks申明、创建、使用
三.Blocks的变量类型(循环引用关系)、__block储存类型(本文重点)

一.Blocks概念、概述

闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称作自由变量)。Block 实际上就是 Objective-C 语言对于闭包的实现。通常来说,Block都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调

Block代表通常是小的,独立的代码片段。因此,它们特别有用,作为可以并发执行的工作单元或者集合中的项目封装的工具,或者当另一个操作完成时作为回调。

Block是传统回调函数的有用替代,主要有两个原因:

  • 它们允许您在方法实现的上下文中执行的调用点编写代码,因此,Block通常是框架方法的参数。
  • 它们允许访问局部变量,而不是使用需要一个体现您执行操作所需的所有上下文信息的数据结构的回调函数,您只需直接访问局部变量即可。

对于官方文档上的这两点有点绕口,理解为,可以在方法的实现中调用Block,同时可以访问局部变量,将这些信息回调到外部结构中去。

二.Blocks申明、创建、使用
2.1声明、创建
int multiplier = 7;
int (^ myBlock)(int) = ^(int num){
   return num * multiplier;
};

下例说明了该示例:


blocks.jpg

注意:
int (^ myBlock)(int) 表示申明了一个名为myBlock的Block指针来保存 ^(int num){return num * multiplier;}对象,如果你对这句话不了解,你可以去看看我的 iOS 程序(APP)运行中的内存分配 这遍文章。

此处你可理解为 UIView *view = [UIView new]; 中的 UIView *view为指向UIView类型的指针,而[UIView new]是生成的一个UIView对象。

此处将代码分成两部分:

  • int (^ myBlock)(int) Block的声明(可比着UIView *view看)
  • ^(int num){return num * multiplier;} Block的实现(可比着[UIView new]看)
2.2 Block使用

显而易见,有了上面的基础,我们就能够大胆的猜测Block的用法了。

  • 1.声明全局的Block。
  • 2.作为局部变量使用。
  • 3.用作属性或者成员变量。
  • 4.作为函数的参数传递。

1.声明全局的Block:

#import "ViewController.h"

/** 全局初始化Block */
void(^kGlobalBlock)(id , id ) = ^(id mayActionType, id parameters){
    NSLog(@"%@,%@",mayActionType,parameters);
};

/** 全局未初始化Block */
void(^kGlobalBlockWaitingInitinalize)(id , id );

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self golbalBlock];
}
#pragma mark - - 全局类型的 Block
- (void)golbalBlock{
    kGlobalBlock(@1,@"Bob");
    kGlobalBlockWaitingInitinalize = ^(id parameterOne, id parameterTwo){
        NSLog(@"parameterOne:%@ \n parameterOne:%@",parameterOne,parameterTwo);
    };
    
    kGlobalBlockWaitingInitinalize(@"Alice",@"Lucy");
    
}
@end

注意,调用Block时必须保证Block已经被初始化,因为此处为Block都在内部实现,Block都是已经初始化。 如果调用了一个未被初始化的Block ,程序将会崩溃。
两种验证Block是否初始化的方法

if (kGlobalBlockWaitingInitinalize) {
    kGlobalBlockWaitingInitinalize(@"Alice",@"Lucy");
}
!kGlobalBlockWaitingInitinalize ? : kGlobalBlockWaitingInitinalize(@"Alice",@"Lucy");

2.作为局部变量使用:
局部变量即在方法、函数中的Block

- (void)viewDidLoad {
    [super viewDidLoad];
    int multiplier = 7;
    int (^ myBlock)(int) = ^(int num){
       return num * multiplier;
    };
    myBlock(3);
}

3.用作属性或者成员变量:

#import "ViewController.h"

typedef NSString * (^BlocksTypeParameter)(id , id);
typedef NSString * (^BlocksTypeNonParameter)();
typedef void (^BlocksTypeNonReture)(id parameterOne, id parameterTwo);

@interface ViewController ()
/* 无参数类型 */
@property (nonatomic, copy) NSString *(^blockProperty)();
/* 有参数类型 */
@property (nonatomic, copy) NSString *(^blockPropertyWithParameters)(NSString *);
/* 无返回值类型 */
@property (nonatomic, copy) void(^blockPropertyWithNonRetern)();
@end


@implementation ViewController{
    /* 有参数类型 */
    BlocksTypeParameter _parameterBlock;
    /* 无参数类型 */
    BlocksTypeNonParameter _nonParemeterBlock;
    /* 无返回值类型 */
    BlocksTypeNonReture _nonRetureBlock ;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    [self propertyBlock];
}

#pragma mark - - 属性、成员变量类型的 Block
- (void)propertyBlock{
    NSString *name = @"Bob";
    // 无参数类型
    _blockProperty = ^{
        NSLog(@"no parameters");
        return name;
    };
    
    // 有参数类型
    _blockPropertyWithParameters = ^(NSString *parameter){
        NSLog(@"has parameters");
        return parameter;
    };
    
    // 无返回值类型
    _blockPropertyWithNonRetern = ^{
        NSLog(@"non retern");
    };
}

4.作为函数的参数传递

- (void)viewDidLoad {
    [super viewDidLoad];
    [self postNetWorkWithParameters:@"parameters" success:^(id response) {
        NSLog(@"%@",response);
    } failure:^(id error) {
        NSLog(@"%@",error);
    }];
}
- (void)postNetWorkWithParameters:(id)parameters
                          success:(void (^)(id response))finishBlock
                          failure:(void (^)(id error))failure{
    finishBlock(@"success");
    failure(@"failure");
}
三.Blocks的变量类型(循环引用关系)、__block储存类型(本文重点)

如果你还在为使用Block照成循环引用而感到苦恼,那么看完这里将会解开你的疑惑
首先来说说官方文档上关于Block可使用的变量归为5类

1.Global variables are accessible, including static variables that exist within the enclosing lexical scope.

2.Parameters passed to the block are accessible (just like parameters to a function).

3.Stack (non-static) variables local to the enclosing lexical scope are captured as const variables. Their values are taken at the point of the block expression within the program. In nested blocks, the value is captured from the nearest enclosing scope.

4.Variables local to the enclosing lexical scope declared with the __block storage modifier are provided by reference and so are mutable. Any changes are reflected in the enclosing lexical scope, including any other blocks defined within the same enclosing lexical scope. These are discussed in more detail in The __block Storage Type.

5.Local variables declared within the lexical scope of the block, which behave exactly like local variables in a function. Each invocation of the block provides a new copy of that variable. These variables can in turn be used as const or by-reference variables in blocks enclosed within the block.

即:

1.全局变量
2.传递给Block的参数
3.方法中局部变量非__block 捕获变量
4.方法中__block 语法修饰的变量
5.Block内声明的变量

3.1 关于__block存储类型:

您可以通过应用__block存储类型修饰符来指定导入的变量是可变的,即读写。

__block变量存储在变量的作用域范围中,以及在变量的作用域中声明或创建的所有BlockBlock Copy之间共享。因此,如果在框架中声明的Block的Copies of the blocks都能在框架的末尾(例如,通过在某个地方排队等待执行),那么存储将会在堆栈的销毁中存活下来。在给定作用域范围内的多个Block可以同时使用一个共享变量。

作为优化,Block存储从堆栈开始,就像Block本身一样。如果这个Block是用block copy复制的,变量会被复制到堆中。因此,__block 变量的地址可以随时间变化。

对于__block变量还有两个进一步的限制:它们不能是variable length arrays,也不能是包含C99 variable length arrays 的结构。

3.2 关于循环引用

如果您在方法的实现中使用了一个Block,当这个Block 被复制时,它会对Block中使用的对象的进行强引用:

那么关于循环引用的造成的原因必然是,在Block Copy 到堆中时Block强引用了这个对象,然后对象又存在强引用Block的情况下就会造成强引用。


不要在所有的Block中都将对象 __weak 或者__weak再__strong、
不要在所有的Block中都将对象 __weak 或者__weak再__strong、
不要在所有的Block中都将对象 __weak 或者__weak再__strong、
重要的事情说三遍。

第一种情况:
在block中使用对象,判断是否造成循环引用,需要判断当前对象是否强引用了block,block是否也强引用了改对象。

第二种情况:
对象A-强引用-对象B,对象B强引用Block,Block中又持有了对象A,也是一种循环引用。 对象A需要Block释放后才能释放,block需要对象B释放后才能释放,对象B需要对象A释放后才能释放,所以三者形成了循环引用。

以下是示例代码

@interface BlockView : UIView
@property (nonatomic, copy) void(^myBlcok)() ;
@end
@implementation BlockView
@end

@interface ViewController ()
@property (nonatomic, copy) NSString *(^blockProperty)();
@property (nonatomic, strong) BlockView *blockView;
@end
- (void)viewDidLoad {
    [super viewDidLoad];

    // case 1
    _blockProperty = ^{
        return self.name;
    };
    _blockView.myBlcok = ^{
        NSLog(@"%@",_blockView); // 关注点不只在self,而是谁持有这个Block。
    };

    // case 2
    _blockView.myBlcok = ^{
        NSLog(@"%@",self.name);
    };
}
B387E9BA-9F1E-47F3-9FEE-238499E9A8CD.png

第一种是在两者之间循环,第二种是在三者之间循环。


以下是对循环引用的解决方案:

__weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(self) strongSelf = weakSelf;
    };

那么对于方法、函数中的Block,大部分只存在Block对 对象进行强引用,不存在对象对Block强引用,此时的Block作为参数先是保存在栈中,然后 被Copy到堆中。

但如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:(此部分参考《招聘一个靠谱的iOS》面试题参考答案(下))

__weak __typeof__(self) weakSelf = self;
  _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                                object:nil
                                                                 queue:nil
                                                            usingBlock:^(NSNotification *note) {
      __typeof__(self) strongSelf = weakSelf;
      [strongSelf dismissModalViewControllerAnimated:YES];
  }];

self --> _observer --> block --> self 显然这也是一个循环引用。

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

推荐阅读更多精彩内容

  • Blocks编程要点 目录 简介............................................
    xuejunjun阅读 1,229评论 0 5
  • Blocks are a non-standard extension added by Apple Inc. t...
    天口三水羊阅读 3,738评论 30 124
  • 一、Objective-C发展史 Objective-C从1983年诞生,已经走过了30多年的历程。随着时间的推移...
    没事蹦蹦阅读 5,832评论 12 34
  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,768评论 0 23
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,139评论 30 470