目录

Block in Objective-C

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) {...};

定义

  1.  Block 是 OC 中的一种数据类型,在 iOS 开发中被广泛使用
  2.  ^ 是 Block 的特有标记
  3.  Block 的实现代码包含在 {} 之间
  4.  大多情况下,以内联 inline 函数的方式被定义和使用
  5.  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 中是只读的!

  1. 在定义 Block 时,会在 Block 中建立当前局部变量内容的副本(拷贝
  2. 后续再对该变量的数值进行修改,不会影响 Block 中的数值
  3. 如果需要在 Block 中保持局部变量的数值变化,需要使用 __block 关键字
  4. 使用 __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 优点

  1. 简单
  2. 所有的代码集中在一起
  3. 代码便于阅读和维护!

代理、 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

说明:

  1. typedef 是关键字用于定义类型,MyBlock 是定义的 Block 类型
  2. 优点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 的场景

  1. 遍历数组或者字典
  2. 视图动画
  3. 排序
  4. 通知
  5. 错误处理
  6. 多线程

在 Block 内部,如果碰到 self ,最好能够思考一下。 再次强调,忘记 BLock 敲一下 inlineBlock