Objective-C 之 Block 基础用法
这两天改bug改的太忙了....实际上该整理的知识点挺多的,今天就先说一下 Block 吧。
本篇文章中大量内容参考、转载自文章末尾的参考1,感谢原作者的总结与分享。原文中还有更多更详细的内容,欢迎大家点击原文学习更多内容。
什么是Block
让我们先简单的介绍一下Block
代码块Block
是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,Block
是一种特殊的数据类型,其可以正常定义变量、作为参数、作为返回值,特殊地,Block
还可以保存一段代码,在需要的时候调用,目前Block已经广泛应用于iOS开发中,常用于GCD、动画、排序及各类回调。
综上我们可以得知:
Block
是一种数据类型。Block
可以做变量,也可以做参数,还可以做为返回值,甚至用来保存一段代码。
Block变量
Block的声明
Block
的声明格式为:返回值(^Block名称)(参数列表),让我们来举个栗子:
NSString *(^aBlock)(NSString *str, NSArray *arr);
如上是一个返回值为NSString
,名称为aBlock
,拥有2个参数,分别为NSString
和NSArray
的一个Block
代码块。
- 注意:
- 其中形参的形参名,即栗子中的
str
和arr
可以省略不记。 - 这里的名称我们便可以将它想象成函数名。
- 其中形参的形参名,即栗子中的
给Block变量赋值
我们声明完Block之后,就该给变量赋值了。
这种情况下的格式为:变量名 = ^返回值类型(参数类型){函数体}。举个栗子:
aBlock = ^NSString *((NSString *str, NSArray *arr) {
NSLog(@"%@, %@", str, arr);
return str;
};
这里实际上我们是把一个函数体赋值给了Block
变量。从这里我们也可以看出来Block
的一个重要用法,那就是用来存储函数体。
- 注意:
- 这里的返回值类型可以不用声明,直接省略。因为编译器可以从存储代码块的变量中确定返回值的类型。
- 如果没有参数列表,在赋值的时候可以省略。
声明的同时赋值
把上面两点综合起来就就可以了,这里直接给个栗子吧。
NSString *(^aBlock)(NSString *, NSArray *) = ^(NSString *str, NSArray *arr) {
NSLog(@"%@, %@", str, arr);
return str;
};
调用Block变量
Block
变量的调用实际上和C语言函数的调用非常接近。为了节省时间,这里的参数str1
和arr
我只进行了初始化,没有赋值。
NSString *str1 = [[NSString alloc] init];
NSArray *arr = [NSArray array];
NSString *str = aBlock(str1, arr);
NSLog(@"%@",aBlock(str1, arr));
是不是和C语言很像呢。
使用typedef定义Block类型
在实际使用Block的过程中,我们可能需要重复地声明多个相同返回值相同参数列表的Block变量,如果总是重复地编写一长串代码来声明变量会非常繁琐,所以我们可以使用typedef来定义Block类型。
// 定义一种无返回值无参数列表的Block类型
typedef void(^SayHello)();
// 我们可以像OC中声明变量一样使用Block类型SayHello来声明变量
SayHello hello = ^() {
NSLog(@"hello");
};
// 调用后控制台输出"hello"
hello();
注意,在使用typedef
的时候,(^)
内的Block名已经被当做了整个Block类型的名称,而不单单是一个Block
的名称。
在函数形参中使用Block
形参使用Block时函数的声明
Block
作为函数形参是一个很重要的用法, 很多知名的第三方库或者网络请求中都会在函数的形参中用到Block
。我们自己写代码的时候,偶尔为了方便高效,或者为了封装,也可以使用Block
。
其格式为:-(返回值)函数名称:(Block返回类型(^)(参数列表))函数参数名
- 注意:
- 为了看起来清晰,在上面的格式中,我使用的都是中文标点,实际使用中肯定都要是英文标点的。
- 在作为形参使用时,将
Block
写在函数定义/声明的形参位置时,Block
没有Block名称。
下面是我自己的在一个项目中定义的一个函数,就使用到了Block
作为形参。在这里大家理解这种声明的格式即可。
- (void)autoresizingMaskOfChildView:(NSMutableArray<NSNumber *> *(^)())autoMaskArr {
NSMutableArray *tempArrM = [NSMutableArray arrayWithArray:autoMaskArr()];
}
上面定义了一个返回值为空void
,名称为autoresizingMaskOfChildView
,拥有一个Block
参数的函数。
其中Block
参数是一个返回值为存储NSNumber
的NSMutableArray
,没有参数列表。
调用形参使用Block的函数
没有参数的Block形参
上面那个栗子中的函数,其调用的时候会是这样的:
[self.view autoresizingMaskOfChildView:^NSMutableArray<NSNumber *> *{
<#code#>
}];
我们在<#code#>
中输入自己的代码,这一部分的代码相当于是赋值给了Block
形参。
有参数的Block形参
我们先定义一个函数,方法中定义了一个block
数据类型参数(返回值为int类型的,且带有一个int
类型的形参)
- (void)calculate:(int (^)(int)) calculateBlock {
//calculateBlock接受外界传入的代码块,也就意味着怎么去操作是由外界调用者决定的
self.result = calculateBlock(_result);//将_result的值作为实参传入
}
外部控制器调用时如下所示:
[manager calculate:^int(int i) {
//参数i自加1,然后返回
i++;
return i;
}];
NSLog(@"%d",manager.result); //输出结果为1
通过最后的NSLog
输出我们可以看到,在外界调用Block
的时候,参数i
的值被传给了函数定义时的参数_result
。相当于_result = i
。
即是外界提供值,然后赋值给Block
内部的变量。而不是从Block
中将值传给外界。
- 注意:上面这种写法实际上有很大问题,我们在这个函数声明的时候,没有说这个
blcok
是不能为空的,那么假如外界在调用这个block
的时候,如果给block
参数的位置传的是nil
,而函数内部又给这个block
传了参数,那么就会有产生Crach
。
例如下面这样:
[manager calculate:nil];
calculate:
函数内部不变,依然是这样:
- (void)calculate:(int (^)(int)) calculateBlock {
self.result = calculateBlock(_result);
}
这样就会导致Crach
,所以我们在使用Block
之前要对Block
做判空处理。
- (void)calculate:(int (^)(int)) calculateBlock {
if (!calculateBlock) {
self.result = calculateBlock(_result);
}
}
使用typedef简化Block
// 1.使用typedef定义Block类型
typedef int(^MyBlock)(int, int);
// 2.定义一个形参为Block的OC函数
- (void)useBlockForOC:(MyBlock)aBlock
{
NSLog(@"result = %d", aBlock(300,200));
}
// 3.声明并赋值定义一个Block变量
MyBlock addBlock = ^(int x, int y){
return x+y;
};
// 4.以Block作为函数参数,把Block像对象一样传递
[self useBlockForOC:addBlock];
// 将第3点和第4点合并一起,以内联定义的Block作为函数参数
[self useBlockForOC:^(int x, int y){
return x+y;
}];
Objective-C 中的block属性
说完了形参与变量,那么在 Objective-C
的属性中该如何使用 block
语法呢?
定义 block 类型
首先我们应该定义一个 block
类型:
// 在#import 与 @interface 之间声明该 block 类型
typedef void(^ButtonEventsBlock)(UIButton *btn);
这样我们就定义了一个返回值为空,名称为 ButtonEventsBlock
,带有一个 UIButton
类型参数的 block
类型了。
声明 block 属性
这一步很简单,就如同声明其他变量一样,使用之前定义好的 block
类型声明一个变量即可。
@property (nonatomic, copy) ButtonEventsBlock buttonEventsBlock;
对 block 属性赋值
这里我将演示两种方法:懒加载 与 Runtime
。
懒加载
在代码中为 block
属性赋值和使用 懒加载 对 block
赋值在语法层面没有太大的差异,所以这里就只演示懒加载了。
那么让我们直接砍代码吧:
- (ButtonEventsBlock)buttonEventsBlock {
if (!_buttonEventsBlock) {
_buttonEventsBlock = ^(UIButton *btn) {
// your code
};
}
return _buttonEventsBlock;
}
我们可以看到,在语法层面上,除了为 block
赋值的那部分外,其余部分与一般的懒加载并无差异。而为 block
赋值的那部分代码我们之前也已经提到过了。
Runtime
如果我们要在分类中使用 block
属性的话,我们就要借住 Runtime
来实现了。因为 Category
中并不会为我们自动生成成员变量。
那么我们代码长什么样子呢?
static void *buttonEventsBlockKey = &buttonEventsBlockKey;
- (ButtonEventsBlock)buttonEventsBlock {
return objc_getAssociatedObject(self, &buttonEventsBlockKey);
}
- (void)setMbc_buttonEventsBlock:(ButtonEventsBlock)buttonEventsBlock {
objc_setAssociatedObject(self, &buttonEventsBlockKey, buttonEventsBlockKey, OBJC_ASSOCIATION_COPY);
}
哈,这里又和一般的在 Category
中使用属性的语法没有什么区别了。只不过,这里我们就没有办法使用懒加载为其赋值了,如果需要默认操作的话,我们只能在代码中对其赋值了。