How Do I Declare A Block in Objective-C?
在 Xcode 中敲 inlineBlock
就可以快捷创建代码块
As a local variable:
1
|
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
|
As a property:
1
|
@property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
|
As a method parameter:
1
|
- (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;
|
As an argument to a method call:
1
|
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
|
As a typedef:
1
2
|
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};
|
定义
- Block 是 OC 中的一种数据类型,在 iOS 开发中被广泛使用
- ^ 是 Block 的特有标记
- Block 的实现代码包含在 {} 之间
- 大多情况下,以内联 inline 函数的方式被定义和使用
- Block 与 C语言 的函数指针有些相似,但使用起来更加灵活
Block的意义:
将一段代码块封装成为一种数据类型,然后可以用符合这种类型定义的 block 变量接收,以后就可以用调用变量的形式,来完成封装的一段代码。
函数的意义 :
也是封装一段代码块,但是他不能当作变量来使用。只能调用的形式来使用
当用 block 类型做函数参数时,要用 if 语句判断穿进来的 block 是否为 nil ,如果不是才调用 block.
示例
1
2
3
4
5
6
7
8
9
|
void(^demoBlock)() = ^
{
NSLog(@"demo Block");
};
int(^sumBlock)(int, int) = ^(int x, int y)
{
return x + y;
};
|
Block 常用方式
当参数传递
Block 可以被当做参数直接传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
NSArray *array = @[@"张三", @"李四", @"王五", @"赵六"];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
NSLog(@"第 %d 项内容是 %@", (int)idx, obj);
if ([@"王五" isEqualToString:obj])
{
*stop = YES;
}
}];
// 打印结果
2016-08-27 21:04:43.138 test[60953:36447944] 第 0 项内容是 张三
2016-08-27 21:04:43.138 test[60953:36447944] 第 1 项内容是 李四
2016-08-27 21:04:43.139 test[60953:36447944] 第 2 项内容是 王五
|
说明: 遍历并打印 array
中的内容, 当 ojb
为 王五
时停止遍历
使用局部变量
在被当做参数传递时,Block 同样可以使用在定义之前声明的局部变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
int stopIndex = 1;
NSArray *array = @[@"张三",
@"李四",
@"王五",
@"赵六"];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
NSLog(@"第 %d 项内容是 %@", (int)idx, obj);
if ([@"王五" isEqualToString:obj] ||
idx == stopIndex)
{
*stop = YES;
}
}];
// 打印结果
2016-08-27 21:21:00.433 test[61035:36536812] 第 0 项内容是 张三
2016-08-27 21:21:00.433 test[61035:36536812] 第 1 项内容是 李四
|
注意,默认情况下,Block 外部的变量,在 BLock 中是只读的!
- 在定义 Block 时,会在 Block 中建立当前局部变量内容的副本(拷贝
- 后续再对该变量的数值进行修改,不会影响 Block 中的数值
- 如果需要在 Block 中保持局部变量的数值变化,需要使用 __block 关键字
- 使用 __block 关键字后,同样可以在 Block 中修改该变量的数值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
BOOL flag = NO;
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
NSLog(@"第 %d 项内容是 %@", (int)idx, obj);
if ([@"王五" isEqualToString:obj] ||
idx == stopIndex)
{
*stop = YES;
flag = YES; // 编译错误
}
}];
//如果要修改 Block 之外的局部变量,需要使用 __block 关键字
__block BOOL flag = NO;
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
NSLog(@"第 %d 项内容是 %@", (int)idx, obj);
if ([@"王五" isEqualToString:obj] ||
idx == stopIndex)
{
*stop = YES;
flag = YES; // 现在可以修改了
}
}];
|
使用 __block 修饰符,可以允许在 block 中修改外部局部变量的数值,一旦定义了使用该变量的 block ,局部变量的地址,就会变成堆区的地址。
传递对象
对象传递进 Block 的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
NSString *stopName = @"王五";
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
NSLog(@"第 %d 项内容是 %@", (int)idx, obj);
if ([stopName isEqualToString:obj])
{
*stop = YES;
}
}];
// 打印结果
2016-08-27 21:33:55.214 test[61132:36595329] 第 0 项内容是 张三
2016-08-27 21:33:55.214 test[61132:36595329] 第 1 项内容是 李四
2016-08-27 21:33:55.214 test[61132:36595329] 第 2 项内容是 王五
|
为保证 Block 中的代码正常运行,在将 stopName 的指针传递给 Block 时,Block 会自动对 stopName 的指针做强引用。
block 排序
升序和降序
1
2
3
4
5
6
7
|
NSArray *result = [array sortedArrayUsingComparator:^NSComparisonResult(NSNumber *number1, NSNumber *number2)
{
// 升序
// return [number1 compare:number2];
// 降序
return [number2 compare:number1];
}];
|
乱序排列
1
2
3
4
5
6
7
8
9
|
int asc = arc4random_uniform(2);
if (asc)
{
return [number1 compare: number2];
}
else
{
return [number2 compare: number1]
}
|
block 优点
- 简单
- 所有的代码集中在一起
- 代码便于阅读和维护!
代理、 Block的选择
代理 (UITalbeView 有11个数据源方法,15个代理方法)
如果代理方法过多,还是比较适合用代理,否则 block 会让代码想当臃肿如果回调方法在1~3个之内,使用block还是非常灵活方便的~
代理和 block 都有循环引用的陷阱,但是代理 block 比较容易被坑,而代理相对比较安全,在开发时,只需要记住: delegate 是 weak 的。
typedef
可以使用 typedef 定义一个 block 的类型, 便于在后续直接使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
typedef double(^MyBlock)(double, double);
MyBlock area = ^(double x, double y)
{
return x * y;
};
MyBlock sum = ^(double a, double b)
{
return a + b;
};
NSLog(@"%.2f", area(10.0, 20.0));
NSLog(@"%.2f", sum(10.0, 20.0));
// 打印结果
2016-08-27 22:26:36.502 test[61448:36884681] 200.00
2016-08-27 22:26:36.503 test[61448:36884681] 30.00
|
说明:
- typedef 是关键字用于定义类型,MyBlock 是定义的 Block 类型
- 优点area、sum 分别是 MyBlock 类型的两个 Block 变量
尽管,typedef 可以简化 Block 的定义,但在实际开发中并不会频繁使用 typedef 关键字.
这是因为 Block 具有非常强的灵活性,尤其在以参数传递时,使用 Block 的目的就是为了立即使用。
官方的数组变量方法声明如下:
1
|
- (void)enumerateObjectsUsingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block ;
|
而如果使用 typedef,则需要:
1
2
3
|
typedef void(^EnumerateBlock)(id obj, NSUInteger idx, BOOL *stop);
- (void)enumerateObjectsUsingBlock:(EnumerateBlock)block ;
|
而最终的结果却是,除了定义类型之外, EnumerateBlock 并没有其他用处。
添加到数组
既然 BLock 是一种数据类型,那么可以将 Block 当做比较特殊的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@property (nonatomic, strong) NSMutableArray *myBlocks;
self.myBlocks = [NSMutableArray array];
typedef double(^MyBlock)(double, double);
MyBlock area = ^(double x, double y)
{
return x * y;
};
MyBlock sum = ^(double a, double b)
{
return a + b;
};
[self.myBlocks addObject:area];
[self.myBlocks addObject:sum];
NSInteger index = 0;
MyBlock func = self.myBlocks[index];
NSLog(@"%.2f", func(10.0, 20.0));
// 打印结果
2016-08-27 23:14:49.866 test[61756:37057766] 200.00
|
循环引用
1
2
3
4
5
6
7
8
|
# pragma mark 将代码改为调用 self 的方法
MyBlock area = ^(double x, double y)
{
return [self doSomethingWithX:x Y:y];
};
[self.myBlocks addObject:area];
|
其中,self 对 myBlocks 强引用,然后 block area 对 self 强引用,后来 block area 加入了看 数组 myBlocks 之中,会被数组强引用,这样就会造成循环引用,这个对象最终不会调用 delloc 方法。
解除循环引用
局部变量默认都是强引用的,离开其所在的作用域之后就会被释放。
使用 __weak
关键字,可以将局部变量声明为弱引用
1
2
3
4
5
6
|
__weak typeof(self) *weakSelf = self;
/// 在 Block 中引用 weakSelf,则 BLokc 不会再对 Self 做强引用
MyBlock area = ^(double x, double y)
{
return [weakSelf doSomethingWithX:x Y:y];
};
|
这样 block area 对 self 就是弱引用了。
一. block 用到外边的对象后,会对它进行强引用,强指针指向它,会造成循环引用,所以要用改用弱指针指向控制器,然后调用这个弱指针。
1
2
3
4
5
|
__weak DemoObj *weakSelf = self;
__weak typeof(self) weakSelf = self;
__unsafe_unretained typeof(self) weakSelf = self;
|
__weak
可以防止野指针错误。
二. 用 typeof()
这样可以不用管类型,它会自动识别类型
在 block 内部不要忘记 strongSelf
在 block 内部最好不要直接用 weakSelf,最好执行一下下面代码
1
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
然后用 self 的地方,都用 strongSelf。
当执行 block 代码的时候,由于在外面使用了__weak 修饰 self,在 block 没有强引用 self,可能正好在 block 里面代码执行的时候,self 突然被释放了,这样的话,就会有一些意料之外的事情发生了,所以在执行 block 代码的开始,先用一个局部变量strongSelf 通过修饰符 __strong 强引用主 self,这时候哪怕别的地方对 self 的引用都没有了,block 内部还有一个,直到 block 内部代码执行结束,strongSelf 销毁,对 self 的引用释放,自然解开了循环引用的结。。这里有一点需要注意,strongSelf 只是保证在 block 内部执行代码的时候 self 不会被释放,但是在执行之前如果没有对象对 self 引用了,就已经被释放了,这样等开始执行 block 内部代码时,self 已经被销毁,所以最好先判断 strongSelf 是否为空。
应用场景
iOS 中使用 Block 的场景
- 遍历数组或者字典
- 视图动画
- 排序
- 通知
- 错误处理
- 多线程
- …
在 Block 内部,如果碰到 self ,最好能够思考一下。
再次强调,忘记 BLock 敲一下 inlineBlock
。