Fork me on GitHub

iOS入门——Block学习

简介

block是在iOS 4.0之后才出现的,它可以将一段代码封装起来作为变量使用。
block声明

返回值 (^block名称) (参数列表…)
如: void (^blockName)(int arg1, int arg2)

block定义(实现)

blockname = ^(int arg1, int arg2){
//do something
}

如下:

1
2
3
int (^addTwoNumber)(int a, int b) = ^(int a, int b) {
return a + b;
};

使用

block的作用类似java中接口回调,但仅仅是作用类似,原理上完全是两个东西。
举例:作为参数回调使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//block无参数
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"ui refresh");
});
//block带参数
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"url" parameters:parameters progress:^(NSProgress *_Nonnull downloadProgress) {
NSLog(@"downloadProgress-->%@", downloadProgress);
} success:^(NSURLSessionDataTask *_Nonnull task, id _Nullable responseObject) {
NSLog(@"success %@", responseObject);
}
} failure:^(NSURLSessionDataTask *_Nullable task, NSError *_Nonnull error) {
NSLog(@"error %@", error);
}];

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
26
27
28
29
30
31
32
33
34
35
36
37
int main(int argc, const char * argv[])
{
@autoreleasepool {
Person *p = [[Person alloc] init];
p.study(@"xx宝典").run().study(@"xx技能");
}
return 0;
}
//Person.h
@interface Person : NSObject
- (Person *(^)(NSString *name))study;
- (Person *(^)())run;
@end
//Person.m
@implementation Person
- (Person *(^)(NSString *))study
{
return ^(NSString *name){
NSLog(@"study----%@", name);
return self;
};
}
- (Person *(^)())run
{
return ^{
NSLog(@"run----");
return self;
};
}
@end

注意:
1、block的内存中有一块是专门来捕获变量的,在声明的block的范围内,所有变量都可以被其捕获。但是对外部变量只能读,如果想要修改,需要给变量加上block关键字修饰。
2、避免循环引用, 使用
weak关键字来修饰需要引用的外部对象,防止导致内存泄漏。
3、可以利用typedef对block进行别称定义,减少代码量,增加可读性,将“对象抽象成类”。比如:typedef void(^SayHello)();

block深究

1、block是一个函数对象,是在运行时产生的,在一个作用域中生成的block对象分配在栈上,离开作用域,就不存在了(可以利用copy将block对象拷贝到堆上)。
每个对象都会有一个isa指针,那么根据isa指针类型,block有三种:
全局静态(globalBlock)/保存在程序的数据区域中(.data区)
出作用域销毁(stackBlock)/保存在栈中,
retainCount=0销毁(mallocBlock)/保存在堆中

2、block访问普通外部变量时,会将变量的值以“const方式”copy一份到block所在内存空间,所以block内部访问的并不是真正的外部变量,而且因为是const方式,所以编译器不允许修改copy的变量。

1
2
3
4
5
6
7
8
9
10
11
12
typedef void (^CustomBlock)(void);
// 外部变量
int value = 0;
NSLog(@"block外部访问:value = %d", value);
NSLog(@"block外部访问:value的地址是:%p", &value);
CustomBlock block = ^{
NSLog(@"block内部访问:value = %d", value);
NSLog(@"block内部访问:value的地址是:%p", &value);
};
block();
NSLog(@"block回到外部访问:value的地址是:%p", &value);

输出:

block外部访问:value = 0
block外部访问:value的地址是:0x7ffeea11a0c4
block内部访问:value = 0
block内部访问:value的地址是:0x600003cc10a0
block回到外部访问:value的地址是:0x7ffeea11a0c4

还有,要注意一点:若将变量定义为__block形式,那么变量的地址将会在block结束后改为block中copy生成的新地址。
3、其实由上可以看出,在block中引用对象,会导致对象的生命周期被延长,特别是当某些大文件被block访问时,有几率会导致内存访问不足。
4、利用block做回调时,在避免循环引用的同时,还要防止引用的对象被提前释放,从而可能会因为操作nil对象导致crash,或者执行一些无意义的逻辑。所以需要我们在block内部操作时加上保护代码。(这种场景在网络访问的情景下较为常见)。

未完待续。。。