第一部分: 理论
什么是闭包
计算机语言中、“闭包(Closure)是由函数和与其相关的引用环境组合而成的实体.” block就是OC对闭包的实现.(很抽象有木有), Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展.
将“函数、函数指针、闭包”三者对比起来理解,能加深对闭包的理解:
函数: 具有特定功能的代码块;
函数指针: 指向函数的指针;
闭包:除具备“函数和函数指针”的所有功能外, 还包括声明它的上下文(如作用域内的自由变量等).
闭包的用途
维基百度科说了3点:
- “惰性求值”特性可用作定义控制语句;
- 多函数使用同一个环境;
- 实现对象系统.
哈哈,除了第二点,其它两点暂时体会不到, OC中主要体现也是第二点,有时候这种结构显得非常简洁直观. 网上一些其它说法:
通常来说,block都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调。
闭包的实现
“典型实现方式是定义一个特殊的数据结构,保存了函数地址指针与闭包创建时的函数的词法环境(also lexical closures or function closures)。”
block的种类
在Objective-C语言中,一共有3种类型的block:
_NSConcreteGlobalBlock 保存在text段的全局的静态block,不会访问任何外部变量。
_NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。
_NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁。
block的使用注意事项
- 在block内直接调用类的实例变量会使self(类的实例)引用计数加1, 这样可能会引起循环引用问题(可以用__weak或local-var处理);
- 使用null的block程序会crash. 使用前判断一下:if(blockVar) {//do something…};
- 在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构。
__block变量和其它变量在block内的读写情况
类型 | 基本数据类型在block内的读写 | block对OBJC对象类型的深浅拷贝 |
---|---|---|
局部自动变量 | 只读 | mutable copy |
全局变量/extern | 读写 | deep copy |
static 变量 | 读写 | deep copy |
__block变量 | 读写 | deep copy |
第二部分: 例子
三种类型的block
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"///>>>>>>>>>>>>> 3种类型的block <<<<<<<<<<<<<<<<<///");
//NSGlobalBlock 全局静态
void(^blkGlobal)(void);
blkGlobal = ^{printf("NSGlobalBlock1");};
NSLog(@"NSGlobalBlock1: %@",blkGlobal);
NSLog(@"NSGlobalBlock2: %@", ^{
printf("NSGlobalBlock2");
});
//NSConcreteStackBlock 栈block
int i = 0;
NSLog(@"NSConcreteStackBlock1: %@", ^{
printf("NSConcreteStackBlock2 :%d",i);
});
//NSConcreteMallocBlock 堆(Heap)block,
void(^blkStack)(void);
//等号是赋值运算,相当于对block进行copy,即相当于调用block的copy方法.
blkStack = ^{ printf("NSConcreteMallocBlock1: %d",i); };
NSLog(@"NSConcreteMallocBlock1: %@",blkStack);
//对栈block进行copy会变成堆block.
NSLog(@"NSConcreteMallocBlock2: %@", [^{
printf("NSConcreteMallocBlock2 :%d",i);
} copy]);
}
}
结果:
NSGlobalBlock1: <__NSGlobalBlock__: 0x1000031d0>
NSGlobalBlock2: <__NSGlobalBlock__: 0x100003210>
NSConcreteStackBlock1: <__NSStackBlock__: 0x7fff5fbff7e0>
NSConcreteMallocBlock1: <__NSMallocBlock__: 0x1002003e0>
NSConcreteMallocBlock2: <__NSMallocBlock__: 0x100400000>
强/循环引用
#import "blockDemo.h"
typedef void(^blk)();
@interface blockDemo(){
int m_var;
}
@property(nonatomic,copy) blk blk;
@end
@implementation blockDemo
- (instancetype)init{
if (self = [super init]) {
[self blockTest];
}
return self;
}
-(void)blockTest{
self.blk = ^{
m_var = 1;
self.str = @"aaa";
};
}
-(void)dealloc{
NSLog(@"blockDemo dealloc");
}
@end
上面的例子dealloc方法不会被执行,因为block内的引用是强引用,在blockTest方法内给blk赋值时直接引用了self和实例变量, 这样会形成循环引用.
解决方法:是用__weak将self变为弱引用.
//来自AFNetworking写法,堪称使用weak–strong的经典。
-(void)blockTest2{
__weak typeof(&*self) weakSelf = self;
self.blk = ^{
__strong typeof(&*weakSelf) strongSelf = weakSelf;
strongSelf->m_var = 1;
weakSelf.str = @"aaa";
};
}
__block变量和各种类型的变量在block内的读写情况
NSInteger CounterGlobal;
int main(int argc, const char * argv[]) {
@autoreleasepool {
extern NSInteger CounterGlobal; //假设在此之前已经有声明.
static NSInteger CounterStatic;
NSInteger localCounter1 = 42 ;
__block NSInteger localCounter2;
void (^aBlock)( void ) = ^( void ){
++CounterStatic; //可以读写。
++CounterGlobal; //可以读写。
++localCounter2; //设定外面定义的localCharacter。
//++localCounter1; //报错,可读不可写.
CounterGlobal = localCounter1; //localCounter1可读不可写.
};
++localCounter1; //不会影响的block 中的值。
localCounter2 = 2 ; //会影响block 中的值.
aBlock(); //执行block 的内容。
NSLog(@"CounterGlobal = %ld, CounterStatic = %ld, localCounter1 = %ld, localCharacter2 = %ld",CounterGlobal,CounterStatic,localCounter1,localCounter2);
}
return 0;
}
结果:
CounterGlobal = 42, CounterStatic = 1, localCounter1 = 43, localCharacter2 = 3
回传例子1 - 参数传递
//blockDemo.h
#import <Foundation/Foundation.h>
typedef void(^blk2)(id obj); //用typedef定义block类型
@interface blockDemo : NSObject
-(void)blockResult:(blk2)complete;
@end
//blockDemo.m
#import "blockDemo.h"
@implementation blockDemo
-(void)blockResult:(blk2)complete{
complete(@"1");
}
@end
这样调用:
blockDemo *blk = [[blockDemo alloc] init];
[blk blockResult:^(id obj) {
NSLog(@"blockResult: %@",obj);
}];
结果:
blockResult: 1
回传例子2 - 用属性扩大block的作用域
重构回传例子1:
//blockDemo.h
#import <Foundation/Foundation.h>
typedef void(^blk2)(id obj); //用typedef定义block类型
@interface blockDemo : NSObject
-(void)blockResult:(blk2)complete;
@end
//blockDemo.m
#import "blockDemo.h"
@interface blockDemo()
@property(nonatomic,copy) blk2 block; //用copy关键字
@end
@implementation blockDemo
-(void)blockResult:(blk2)complete{
//complete(@"1");
_block = complete;
[self blockReturn];
}
-(void)blockReturn{
if (_block) { //执行空的block会使程序crash
_block(@"2");
}
}
@end
这样调用:
blockDemo *blk = [[blockDemo alloc] init];
[blk blockResult:^(id obj) {
NSLog(@"blockResult: %@",obj);
}];
结果:
blockResult: 2
回传例子3 - 用block代替delegate
封装UIButton, 实现点击button后回调方法,用block实现:
GVBlockUIButton.h:
#import <UIKit/UIKit.h>
@class GVBlockUIButton;
typedef void(^GVBlockCallback)(GVBlockUIButton*);
@interface GVBlockUIButton : UIButton
@property(nonatomic,copy) GVBlockCallback clicked;
@end
GVBlockUIButton.m
#import "GVBlockUIButton.h"
#define returnInstance \
if (self) { \
[self blockBtnInit]; \
} \
return self;
@implementation GVBlockUIButton
-(instancetype)init{
self = [super init];
returnInstance
}
-(instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
returnInstance
}
-(id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
returnInstance
}
-(void)blockBtnInit{
[self addTarget:self action:@selector(btnAction:) forControlEvents:UIControlEventTouchUpInside];
}
-(void)btnAction:(GVBlockUIButton*)sender{
if (_clicked) {
_clicked(self);
}
}
@end
调用:
...
GVBlockUIButton *btn = [[GVBlockUIButton alloc] init];
[btn setTitle:@"Block回调测试" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[btn setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted];
[btn setFrame:CGRectMake(10, 100, btn.intrinsicContentSize.width, btn.intrinsicContentSize.height)];
btn.clicked = ^(GVBlockUIButton* sender){
NSLog(@"clicked...");
};
...
点击button后输出结果:
clicked...
代码简洁直观
block有时候显得很简洁直观,看看OC中的对数组排序的例子:
- (void)arraySortTest{
NSArray *arr = @[
@{@"name":@"a", @"age":@"1"},
@{@"name":@"c", @"age":@"3"},
@{@"name":@"d", @"age":@"4"},
@{@"name":@"b", @"age":@"2"}
];
NSArray *sortArr = [arr sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [[obj1 valueForKey:@"age"] compare:[obj2 valueForKey:@"age"]]; //升序
}];
NSLog(@"%@",sortArr);
}
结果:
sortArr = (
{
age = 1;
name = a;
},
{
age = 2;
name = b;
},
{
age = 3;
name = c;
},
{
age = 4;
name = d;
}
)
类似地还有降序:
//降序方法1
return [[obj1 valueForKey:@"age"] compare:[obj2 valueForKey:@"age"]] * -1;
//降序方法2, 推荐
switch ([[obj1 valueForKey:@"age"] compare:[obj2 valueForKey:@"age"]]) {
case NSOrderedAscending: return NSOrderedDescending; break;
case NSOrderedDescending: return NSOrderedAscending; break;
default: return NSOrderedSame; break;
}
__block的实现
前面提到block典型的实现方式是定义一个特殊的数据结构保存block的函数和上下文, 哪这个数据结构长什么样子呢, 下面用clang 的 -rewrite-objc 参数看看oc的block的c源码是如何实现的:
- _NSConcreteGlobalBlock类型的block实现:
用xcode创建一个OSX的”Command Line Tool”项目, 语言选择”c”, 编辑源代码如下:
// main.c
#include <stdio.h>
int globalI = 1;
int main() {
^{printf("block test!\n");}();
printf("%p\n%p\n",^{printf("block test!\n");}, &globalI);
return 0;
}
打印结果:
block test!
0x100001080
0x1000010a0
从打印的地址类型可看出,是一个GlobalBlock类型的block. 执行以下命令:
clang -rewrite-objc main.c
会生成一个main.app的源文件, 它就是oc的c源码翻译,简化一下大概有以下内容:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; //clang和LLVM的实现有可能不一样, clang我只见_NSConcreteStackBlock类型,没有看见其它两种类型.
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("block test!\n");
}
int main() {
(void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)();
return 0;
}
__main_block_func_0就是block的函数指针, 由于GlobalBlock类型的block没有访问任何外部变量, 所以我再看看其它类型的block是怎么处理上下文的:
2._NSConcreteStackBlock类型的block实现:
将以下oc源码翻译和c源码:
#include <stdio.h>
int main() {
int ai = 10;
^{printf("NSConcreteStackBlock,%d",ai);}();
return 0;
}
主要内容如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int ai;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _ai, int flags=0) : ai(_ai) {
impl.isa = &_NSConcreteStackBlock; //clang和LLVM的实现有可能不一样, clang我只见_NSConcreteStackBlock类型,没有看见其它两种类型.
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int ai = __cself->ai; // bound by copy
printf("NSConcreteStackBlock,%d",ai);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main() {
int ai = 10;
(void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, ai)();
return 0;
}
从语句int ai = __cself->ai;
得知, auto局部变量只是简单的复制, 不影响外部值, 相当于函数参数的值传递.
3._NSConcreteMallocBlock类型的block
将以下OC源码翻译为c源码现实:
#include <stdio.h>
int main() {
__block int ai = 10;
void(^blockVar)(int);
blockVar = ^(int i){
printf("NSGlobalBlock1,%d",ai);
};
return 0;
}
主要内容如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_ai_0 {
void *__isa;
__Block_byref_ai_0 *__forwarding;
int __flags;
int __size;
int ai;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_ai_0 *ai; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_ai_0 *_ai, int flags=0) : ai(_ai->__forwarding) {
impl.isa = &_NSConcreteStackBlock; //clang和LLVM的实现有可能不一样, clang我只见_NSConcreteStackBlock类型,没有看见其它两种类型.
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int i) {
__Block_byref_ai_0 *ai = __cself->ai; // bound by ref
printf("_NSConcreteMallocBlock,%d",(ai->__forwarding->ai));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->ai, (void*)src->ai, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->ai, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main() {
__attribute__((__blocks__(byref))) __Block_byref_ai_0 ai = {(void*)0,(__Block_byref_ai_0 *)&ai, 0, sizeof(__Block_byref_ai_0), 10};
void(*blockVar)(int);
blockVar = (void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_ai_0 *)&ai, 570425344);
return 0;
}
__block类型实际是__Block_byref_ai_0类型, 这里很容易看出, 是将变量的地址传入block了,相当于函数的地址传递.
第三部分: 测试
1.预计以下程序打印结果:
#include <stdio.h>
int globalVar = 0;
int main() {
extern int globalVar;
static int staticVar = 0;
int localVar1 = 0 ;
__block int localVar2 = 0;
void(^blockVar)(int);
blockVar = ^(int i){
printf("globalVar 3 = %d staticVar 3 = %d localVar1 3 = %d localVar2 3 = %d\n", globalVar, staticVar, localVar1, localVar2);
++globalVar;
++staticVar;
localVar2 = localVar1 + i;
printf("globalVar 4 = %d staticVar 4 = %d localVar1 4 = %d localVar2 4 = %d\n", globalVar, staticVar, localVar1, localVar2);
};
printf("globalVar 1 = %d staticVar 1 = %d localVar1 1 = %d localVar2 1 = %d\n", globalVar, staticVar, localVar1, localVar2);
++globalVar;
++staticVar;
++localVar1;
++localVar2;
printf("globalVar 2 = %d staticVar 2 = %d localVar1 2 = %d localVar2 2 = %d\n", globalVar, staticVar, localVar1, localVar2);
blockVar(2);
printf("globalVar 5 = %d staticVar 5 = %d localVar1 5 = %d localVar2 5 = %d\n", globalVar, staticVar, localVar1, localVar2);
return 0;
}
鼠标选择以下内容查看答案:
globalVar 1 = 0 staticVar 1 = 0 localVar1 1 = 0 localVar2 1 = 0
globalVar 2 = 1 staticVar 2 = 1 localVar1 2 = 1 localVar2 2 = 1
globalVar 3 = 1 staticVar 3 = 1 localVar1 3 = 0 localVar2 3 = 1
globalVar 4 = 2 staticVar 4 = 2 localVar1 4 = 0 localVar2 4 = 2
globalVar 5 = 2 staticVar 5 = 2 localVar1 5 = 1 localVar2 5 = 2
2.以下两种情况,会不会影响对象释放? 如果会,该如何修改,使对象能正常释放内存?
//代码块1
typedef void(^blk)();
@interface blockDemo(){
int m_var;
}
@property(nonatomic,strong) NSString *str;
@property(nonatomic,copy) blk blk;
@end
@implementation blockDemo
- (instancetype)init
{
self = [super init];
if (self) {
[self blockTest];
}
return self;
}
-(void)blockTest{
self.blk = ^{
m_var = 1;
self.str = @"aaa";
};
}
@end
//代码块二
@interface blockDemo(){
int m_var;
}
@property(nonatomic,strong) NSString *str;
@end
@implementation blockDemo
- (instancetype)init
{
self = [super init];
if (self) {
self.str = @"blockTest!";
m_var = 10;
^{NSLog(@"%@,%d",self.str,m_var);}();
}
return self;
}
@end
鼠标选择以下内容查看答案:
1. self的属性的内容里又引用了self, 导致循环引用. 解决方法用__weak将self变为弱引用, 见本文第一部分”强/循环引用”节.
2. 没有内存释放不了的问题, 虽然block内是强引用, 但当block释放后, 它持有的对象的引用计数也会恢复.
第三部分: 参考链接
http://ff20081528.iteye.com/blog/1670433
http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
https://zh.wikipedia.org/wiki/%E9%97%AD%E5%8C%85_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)