Working with Blocks

简介

Blocks是C语言层级语法和运行时特性。 它们类似于标准C函数,但是除了可执行代码之外,它们还可以保存堆栈变量。 因此,块可以保存数据,在代码执行时使用。
1、Block可以作为函数数调用、作为函数参数、作为方法参数。
2、因为独立完整可以在多线程中使用;
3、因为拥有回调时需要执行的代码和执行代码时需要的数据,常常被用来实现回调Callback。
由于Objective-C和C++都是从C派生,因此三种语言均可以使用(Objective-C使用更多)。iOS从4.0版本开始支持。在其他语言环境中有时被称为“闭包”。

特点

Block是一个匿名的内联代码集合,有以下特点:
1、像函数一样用参数列表
2、有可推断或者声明的返回值类型
3、可以从定义它作用域捕获数据
4、可选的可以修改捕获到的数据
5、同一作用域中的其它block共享捕获的数据
6、作用域的堆栈被销毁后,仍然可以继续共享定义其范围定义的数据
由于compiler 和 runtime 保证block和其相关的数据的生命周期,因此可以copy一份传递到其他地方使用。

声明和创建

声明

首先说明下函数指针的声明格式

返回值类型 ( * 指针变量名) ([形参列表]);

Block variables保存着block的引用。可以像声明函数指针一样声明block变量,但是要把*换成^,例如以下均为有效的声明

void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);

Blocks支持可变的参数列表,当没有参数时必须写void。
通过为编译器提供block使用的数据,传递参数,返回值,block设计完全类型安全。可以把block引用强制转换成任意类型的指针,反之亦然。但是不能使用操作符*来访问值,因为block的值在编译期无法计算。
理解:假如每次方法调用当做一次消息发送(一般底层会有很多次),把block中的所以消息发送按照顺序放到一种能够先进先出的数据结构--比方说实现栈特性的结构体;那么block变量^{ ... }作用一样都是指向结构体的首地址;传递首地址跟其他对象指针、函数指针用法很像。

类型定义简化用法

如果经常重复使用同一个类型的block,你可以定义自己的block类型,例如

//返回值类型:float 两个参数类型:float 变量名称:MyBlockType
typedef float (^MyBlockType)(float, float);
 
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;

创建

^表示block的开始,接一个返回值类型(可选,默认不写)再接一个(参数列表),后边接一个{代码块};例如

int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
    return num * multiplier;
};

解读如下图所示



如果没有明确声明返回值类型,则根据block代码内容推断具体类型,例如

int multiplier = 7;

//未指明返回值类型
int (^myBlock)(int) = ^(int num) {
    return num * multiplier;
};

//指明返回值类型
int (^myBlock)(int) =  ^ int (int num) {
   return multiplier;
};

Blocks和变量

首先可以像函数一样引用三种标准类型的变量:
1、全局变量,包括静态局部变量
2、全局函数
3、作用域内的局部变量和参数
其次Blocks还支持另外两种类型:
4、__block修饰的变量
5、const导入的
所以Blocks内部处理变量的五种情况:
1、全局变量(包括作用域内的静态局部变量)可以直接访问
2、Blocks的参数可以直接访问
3、作用域内非静态局部变量,作为const变量引用(只读),强行修改编译器报错
4、被__block修饰的作用域内非静态局部变量,可以直接访问
5、Blocks内部声明的局部变量,可以直接访问
示例代码如下

int global_var = 1;

- (void)viewDidLoad {
[super viewDidLoad];
    
   
static int static_var = 2;
    
__block int loacal_var = 3;

__block int const_var = 3;

void (^myBlock)(int) = ^(int number) {

   global_var = global_var * 10;
   static_var = static_var * 10;
   loacal_var = loacal_var * 10;
   number     = number * 10;
   NSLog(@"局部变量:%d ,说明局部变量可以读取",const_var);
   NSLog(@"参数变量原:4 修改后%d,说明可以正常访问",number);

};
myBlock(4);

NSLog(@"全局变量原:1 修改后:%d,说明可以正常访问 ",global_var);
NSLog(@"静态变量原:2 修改后:%d,说明可以正常访问 ",static_var);
NSLog(@"__block修饰的局部变量原:3 修改后:%d,说明可以正常访问 ",loacal_var);
}

//输出结果
局部变量:3 ,说明局部变量可以读取
参数变量原:4 修改后40,说明可以正常访问
全局变量原:1 修改后:10,说明可以正常访问 
静态变量原:2 修改后:20,说明可以正常访问 
__block修饰的局部变量原:3 修改后:30,说明可以正常访问 

Blocks和对象

对象通过Properties来引用Blocks。语法和定义Blocks语法类似;例如

@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end

self.blockProperty = ^{
   ...
};
self.blockProperty();

注意:此处应该用copy,因为block需要被copy来保存引用的外界的变量,无需关心引用计数问题,编译器自动进行管理。

还可以通过自定义类型简化

typedef void (^XYZSimpleBlock)(void);
 
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end

避免循环引用

当变量变成实例变量的时候,需要避免循环引用的问题。由于Blocks会持有使用的变量,比如在Viewcontroller中,block作为一个property被self持有,block中使用self调用方法,这样便造成循环引用的问题使得二者在不使用时均得不到释放。解决方法是在block中使用self的弱引用,用__weak修饰。

@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end

- (void)configureBlock {
    XYZBlockKeeper * __weak weakSelf = self;
    self.block = ^{
        [weakSelf doSomething];   // capture the weak reference
                                  // to avoid the reference cycle
    }
}

用法

作为变量

如果声明Block作为一个变量,可以像函数一样调用。例如

int (^oneFrom)(int) = ^(int anInt) {
    return anInt - 1;
};
 
printf("1 from 10 is %d", oneFrom(10));
// Prints "1 from 10 is 9"
 
float (^distanceTraveled)(float, float, float) =
                         ^(float startingSpeed, float acceleration, float time) {
 
    float distance = (startingSpeed * time) + (0.5 * acceleration * time * time);
    return distance;
};
 
float howFar = distanceTraveled(0.0, 9.8, 1.0);
// howFar = 4.9

作为函数参数

可以像传递其它参数一样传递block。大多数的情况不需要声明,只需要作为参数以内联的方式实现。例如

char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };

//根据首字符进行一次排序 
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
    char *left = *(char **)l;
    char *right = *(char **)r;
    //此函数表示:以参数3的长度计算前2个参数的差值
    return strncmp(left, right, 1);
});

// Block implementation ends at "}"
 
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }

作为方法参数

Cocoa Touch 提供了很多方法使用Blocks。可以传递block作为参数使用。例如

//是否包含指定字符串
__block BOOL found = NO;
NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil];
NSString *string = @"gamma";
 
[aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
    if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
        *stop = YES;
        found = YES;
    }
}];
 
// At this point, found == YES

作用

很大程度上起到了简化的作用。

简化回调

Blocks可以用处替换传统回调的原因如下:
1、方法调用和回调代码集中在一起;还常作为framework methods的参数。
2、可以直接访问局部变量。

简化枚举

对一个数组进行枚举,常用的方法有3种
1、for循环
2、for的泛型遍历for (type *object in collection)
3、使用带有block的简化方法。例如- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;

NSArray *array = @[@"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @"Q"];

//for循环
for (int i = 0; i < array.count; i ++)
{
   
   NSString *item = array[i];
   NSLog(@"index:%d value:%@",i,item);
}
 
 //泛型遍历   
for (NSString *item in array)
{
   NSLog(@"value:%@",item);
}
 
 //枚举方法   
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
   NSLog(@"index:%ld value:%@",idx,obj);
   
}];

简化并发任务

每个block都是独立的工作单元,包含可执行代码和保存使用的数据,这使得它能够在多线程开发中被异步调用。
系统提供了一系列的多线程编程技术,其中包括两种任务调度机制:Operation queues 和 Grand Central Dispatch(GCD)。不像Thread关注怎么样管理运行,把需要执行的任务打包到blocks中,然添加到队列中,然后交给系统根据当时的资源自动执行。

简化Operation Queues

//定义任务
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    ...
}];

// 主线程执行
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
 
// 后台执行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];

更多知识请阅读Operation Queues

简化Grand Central Dispatch

//获取并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//定义任务添加到队列并执行
dispatch_async(queue, ^{
    NSLog(@"Block for asynchronous execution");
});

更多知识请阅读 Dispatch Queues

参考文献:Blocks Programming TopicsWorking with Blocks

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

推荐阅读更多精彩内容

  • 在编程领域里,一个牛逼程序员和一个二逼程序员之间的区别主要是其对所用编程语言优秀特性的运用方式。要说到Object...
    Jimmy_L_Wang阅读 462评论 0 1
  • Blocks编程要点 目录 简介............................................
    xuejunjun阅读 1,195评论 0 5
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,121评论 29 470
  • 天秤座真的是纠结病的祖宗。。。说服自己需要几光年距离的行走时间。。。 不管怎么样,今天给Department He...
    匿称也不行阅读 200评论 0 0
  • 你能不能晚上乖乖睡觉?
    小宝尼阅读 164评论 0 0