Objective-C block深入理解
一、block是什么?
block是带有自动变量(局部变量)的匿名函数。它是C语言的扩展功能,C语言标准并不支持block。
block是Objective-C的闭包实现,正如C++中的Lambda表达式。闭包简单理解即函数中的函数,闭包在JavaScript中是一个很重要的概念。作为“函数中的函数”,block跟函数很类似。
1、block变量 VS 函数指针变量
return_type (^block_name) (parameter list) // ^标识block,参数名可省略
int (^blk) (int, int) = ^(int a, int b) { return a+b; }; // 创建一个匿名block,并赋值给一个名为blk的block变量 int ret = blk(10, 20); // block调用
return_type (*fptr_name) (parameter list) // 函数指针,参数名可省略
int f(int a, int b) { return a+b; } // 函数定义 int (*fptr)(int, int); // 函数指针声明 fptr = f; // 函数名f其实就是个指针 int ret = fptr(10, 20); // 通过函数指针调用函数
通常的变量定义语句是像下面这样的,但是block变量的语法形式比较难记:
变量类型 变量名 = 变量值;
为此,可以用typedef给block变量类型起个简单的别名
typedef int (^blk) (int, int); // 相比正常的block变量声明,只是前面多了typedef,blk就变成了这种block变量类型的别名。 blk sumBlk = ^(int a, int b) { return a+b; }; // 这样就回到熟悉的变量定义语句格式了
2、block也是一种对象,根据它在内存中的位置,分为全局block、栈block和堆block。可以通过NSLog打印识别具体是哪一种block对象。
1)没引用外部变量的是全局block
int a = 1; void (^blk) () = ^{ //NSLog(@"%d", a); }; NSLog(@"%@", blk); // <__NSGlobalBlock__: 0x102d23be0>
2)引用了外部变量,在MRC下是栈block,可以通过[block copy]把栈block拷贝到堆上,转成堆block;在ARC下,很多时候系统帮我们完成了拷贝,具体什么时候,要实测。
int a = 1; void (^blk) () = ^{ NSLog(@"%d", a); }; NSLog(@"%@", blk); // ARC下 <__NSMallocBlock__: 0x60400005ba50> // MRC下 <__NSStackBlock__: 0x7ffeefbff518>
3)栈block,类似局部变量,出了它的作用域,会被系统回收内存,再调用可能导致程序崩溃。
4)堆block,正如其他Objective-C对象那样,利用引用计数进行内存管理。
3、在block里面,默认不能修改外部变量。这里指的是不能修改变量本身,如果是指针类型,它指向的内容是可以修改的。
局部变量
全局变量
静态变量
__block变量
成员变量
属性
- (void)testBlock { int i = 10; NSLog(@"[%p] [%d]", &i, i); void (^blk)() = ^(){ NSLog(@"[%p] [%d]", &i, i); i++; // 不能修改,报错:Variable is not assignable (missing __block type specifier) }; blk(); } 输出: [0x60800044e810] [10] [0x60800044e810] [10]
- (void)testBlock { NSMutableString *str = [[NSMutableString alloc] initWithString:@"abc"]; NSLog(@"[%p] [%@]", str, str); void (^blk)() = ^(){ NSLog(@"[%p] [%@]", str, str); [str appendString:@"def"]; // 可以修改指针指向的内容 str = [[NSMutableString alloc] initWithString:@"another str"]; // 不能修改指针本身,报错:Variable is not assignable (missing __block type specifier) }; blk(); NSLog(@"[%p] [%@]", str, str); } 输出: [0x60000025df10] [abc] [0x60000025df10] [abc] [0x60000025df10] [abcdef]
struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); }; struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ };
5、循环引用
1)原因:对象A有block属性,即持有block;而block中又用了对象A,使得block也持有了对象A。
self.blk = ^{ [self method]; };
2)
__weak typeof(self) weakSelf = self; self.blk = ^{ __strong typeof(weakSelf) strongSelf = weakSelf; [self method]; };
block不会捕获形参到内部持有
block也是对象
自动型,托管型,变量绑定
========================================================
循环引用
成员变量
持有对象的成员变量即间接持有对象。
__block修饰符可以用来避免循环引用?block不会持有__block对象。
__block修饰符在MRC和ARC下区别很大?