1. Block介绍和定义
Block是什么? Block是OC对于闭包的实现. 什么是闭包? 闭包是一个函数(或者指向函数的指针) + 自由变量(上下文变量)所组成的.
首先我们不要被Block所吓倒, 认为太高大上了, 其实我们就拿它当一个对象类型来用就可以了
我们平常是怎么定义一个变量的呢?
int variable = 10;
Person * person = [Person new];
我们来看, 定义一个Block类型的变量是怎么样的!
返回值(^变量名)(参数类型1, 参数类型2, ...) = ^(参数类型 参数1, 参数类型 参数2, ...){语句体};
void(^block)(void) = ^(){ // todo };
这段代码: 我们定义了一个Block, 名为block, 类型为void(^)(void), 给变量赋值为 ^(){ // todo };
既然Block也是一种数据类型, 我们可以给Block起一个别名, 来方便我们使用, 如下:
typedef void(^MyBlock)(void);
这样我们就把void(^)(void)这种Block类型用MyBlock来代替, 我们可以直接使用MyBlock来定义一个Block变量
MyBlock myBlock = ^(){ // todo };
相同的Block类型的变量还可以进行赋值操作
myBlock = block;
2. Block使用
我们可以定义四种类型的Block
typedef void(^NoReturnValueNoParam)(void); // 无返回值, 无参数 typedef void(^NoReturnValueHaveParam)(NSString *); // 无返回值, 有参数 typedef NSString * (^HaveReturnValueNoParam)(void); // 有返回值, 无参数 typedef NSString * (^HaveReturnValueHaveParam)(NSString *, NSString *); // 有返回值, 有参数
/** a. 没有返回值, 没有参数 */ void(^sayHello)(void) = ^(){ NSLog(@"Hello!"); }; sayHello(); /** b. 没有返回值, 有参数 */ NoReturnValueHaveParam sayHelloTo = ^(NSString * name){ NSLog(@"Hello, %@!", name); }; sayHelloTo(@"Ray"); /** c. 有返回值, 没有参数 */ HaveReturnValueNoParam getValue = ^(){ return @"Ray"; }; NSLog(@"%@", getValue()); /** d. 有返回值, 有参数 */ HaveReturnValueHaveParam joint = ^(NSString * a, NSString * b){ return [NSString stringWithFormat:@"%@%@", a, b]; }; NSLog(@"%@", joint(@"Ray", @"Lee"));
3. Block种类
根据Block存储位置的不同, 可以将Block分为三类: __NSGlobalBlock__, __NSMallocBlock__, __NSStackBlock__
a. __NSGlobalBlock__
(1). 没有捕获外部变量的Block
int main(int argc, const char * argv[]) { @autoreleasepool { void(^globalBlock)(void) = ^(){ NSLog(@"global block"); }; globalBlock(); NSLog(@"%@", [globalBlock class]); } return 0; }
(2). 使用了全局变量
/** 全局变量 */ int variable = 10; int main(int argc, const char * argv[]) { @autoreleasepool { void(^globalBlock)(void) = ^(){ NSLog(@"%d", variable); }; globalBlock(); NSLog(@"%@", [globalBlock class]); } return 0; }
(3). 使用了全局静态变量
/** 全局静态变量 */ static int staticGlobalVariable = 20; int main(int argc, const char * argv[]) { @autoreleasepool { void(^globalBlock)(void) = ^(){ NSLog(@"%d", staticGlobalVariable); }; globalBlock(); NSLog(@"%@", [globalBlock class]); } return 0; }
(4). 使用了局部静态变量
int main(int argc, const char * argv[]) { @autoreleasepool { /** 局部静态变量 */ static int localStaticVariable = 30; void(^globalBlock)(void) = ^(){ NSLog(@"%d", localStaticVariable); }; globalBlock(); NSLog(@"%@", [globalBlock class]); } return 0; }
b. __NSMallocBlock__
(1). 使用了Block外部的局部变量, 且用正常变量来接收这个Block
int localVariable = 10; void(^mallocBlock)(void) = ^(){ NSLog(@"%d", localVariable); }; mallocBlock(); NSLog(@"%@", [mallocBlock class]);
c. __NSStackBlock__
(1). 使用了Block外部的局部变量, 且用weak变量来接收这个Block
__weak void(^weakBlock)(void) = ^(){ NSLog(@"%d", localVariable); }; weakBlock(); NSLog(@"%@", [weakBlock class]);
4. Block与外部变量
注意: a. 默认情况下, Block只读读取自由变量的值, 不能修改自由变量的值 b. 想要修改自由变量的值, 需要用__block修饰 c. __block将修饰的变量变成了对象
a. Block与基本数据类型
int a = 10; NSLog(@"Block定义前a的地址 - %p", &a); void(^blockOne)(void) = ^(){ NSLog(@"Block定义内a的地址 - %p", &a); }; NSLog(@"Block定义后a的地址 - %p", &a); blockOne(); NSLog(@"Block调用后a的地址 - %p", &a);
控制台打印:
Block定义前a的地址 - 0x7ffeefbff5ec Block定义后a的地址 - 0x7ffeefbff5ec Block定义内a的地址 - 0x100407c40 Block调用后a的地址 - 0x7ffeefbff5ec
从控制台打印中可以看出, Block内部a的地址发生了改变, 由此我们可以知道, Block内部a和Block外部a指向的不是同一块内存, 内部a是由外部a通过拷贝到堆中的.
b. Block与__block修饰的基本数据类型
__block int b = 10; NSLog(@"Block定义前b的地址 - %p", &b); void(^blockTwo)(void) = ^(){ NSLog(@"Block定义内b的地址 - %p", &b); }; NSLog(@"Block定义后b的地址 - %p", &b); blockTwo(); NSLog(@"Block调用后b的地址 - %p", &b);
控制台打印:
Block定义前b的地址 - 0x7ffeefbff5b0 Block定义后b的地址 - 0x100504b18 Block定义内b的地址 - 0x100504b18 Block调用后b的地址 - 0x100504b18
从控制台打印中可以看出, Block内部b的地址发生了改变, 并且Block定义后, Block调用后, 都是Block内部b的地址, 由此我们可以知道, Block将外部b拷贝一份到堆内存中, 并且使外部b和内部b是同一个, 也就是代表同一块内存.
c. Block与指针
NSString * c = @"abcdefg"; NSLog(@"Block定义前: c = %@, c指向的地址 = %p, c本身的地址 = %p", c, c, &c); void(^blockThree)(void) = ^(){ NSLog(@"Block定义内: c = %@, c指向的地址 = %p, c本身的地址 = %p", c, c, &c); }; NSLog(@"Block调用前: c = %@, c指向的地址 = %p, c本身的地址 = %p", c, c, &c); blockThree(); NSLog(@"Block调用后: c = %@, c指向的地址 = %p, c本身的地址 = %p", c, c, &c);
控制台打印:
Block定义前: c = abcdefg, c指向的地址 = 0x100002218, c本身的地址 = 0x7ffeefbff550 Block调用前: c = abcdefg, c指向的地址 = 0x100002218, c本身的地址 = 0x7ffeefbff550 Block定义内: c = abcdefg, c指向的地址 = 0x100002218, c本身的地址 = 0x100705ce0 Block调用后: c = abcdefg, c指向的地址 = 0x100002218, c本身的地址 = 0x7ffeefbff550
从控制台打印中可以看出, 和使用基本数据类型是一样的, Block内部c是由Block外部c拷贝一份到堆中的, 内部c与外部c所指向的内存是同一块.
d. Block与__block修饰的指针
__block NSString * d = @"hijklmn"; NSLog(@"Block定义前: d = %@, d指向的地址 = %p, d本身的地址 = %p", d, d, &d); void(^blockFour)(void) = ^(){ NSLog(@"Block定义内: d = %@, d指向的地址 = %p, d本身的地址 = %p", d, d, &d); }; NSLog(@"Block定义后: d = %@, d指向的地址 = %p, d本身的地址 = %p", d, d, &d); blockFour(); NSLog(@"Block调用后: d = %@, d指向的地址 = %p, d本身的地址 = %p", d, d, &d);
控制台打印:
Block定义前: d = hijklmn, d指向的地址 = 0x1000022b8, d本身的地址 = 0x7ffeefbff518 Block定义后: d = hijklmn, d指向的地址 = 0x1000022b8, d本身的地址 = 0x1006426c8 Block定义内: d = hijklmn, d指向的地址 = 0x1000022b8, d本身的地址 = 0x1006426c8 Block调用后: d = hijklmn, d指向的地址 = 0x1000022b8, d本身的地址 = 0x1006426c8
从控制台打印中可以看出, 和__block修饰的基本数据类型是一样的, Block内部d的地址发生了改变, 并且Block定义后, Block调用后, 都是Block内部d的地址, 由此我们可以知道, Block将外部d拷贝一份到堆内存中, 并且使外部d和内部d是同一个, 也就是同一块内存.
5. Block造成的循环引用
a. 如果一个类将Block作为自己的属性变量, 然后该类在Block内, 又使用了自己本身, 就会造成循环引用!
我们需要这样做: 在Block外部, __weak typeof(self) weakSelf = self; 可以保证一个类的对象和Block不互相持有.
b. 用__weak修饰对象, 当外部对象释放了之后, Block内部也访问不到这个对象怎么办?
我们需要这样做: 在Block内部, __strong typeof(self) strongSelf = self; 可以保证至少在Block内部self是存在的, 不会被销毁的.