一、Block声明
1、普通声明
returnType为返回值 ,blockName为block的名称, parameter为参数,多个参数用,隔开
returnType (^blockName) (parameter) = ^ returnType (parameter){};
上面为Block的完整声明,像返回值,参数等是可以省略的,如下
void (^block1)(void) = ^{
NSLog(@"没有返回值,没有参数的最简写法");
};
void (^block2)(void) = ^void (void){
NSLog(@"没有返回值,没有参数的完整的写法");
};
void (^block3)(void) = ^(void){
NSLog(@"没有返回值,没有参数的只写参数");
};
//------------------------------------------
NSInteger (^block4)(void) = ^NSInteger (void){
NSLog(@"有返回值,没有参数");
return 3;
};
NSInteger (^block5)(void) = ^NSInteger {
NSLog(@"有返回值,没有参数简写");
return 3;
};
//-------------------------------------------
void (^block6)(NSInteger num) = ^void (NSInteger num){
NSLog(@"没有返回值,有参数");
};
void (^block7)(NSInteger num) = ^(NSInteger num){
NSLog(@"没有返回值,有参数简写");
};
//block调用
block1();
block2();
block3();
block4();
block5();
block6(2);
block7(3);
2、匿名声明
//匿名block调用时,直接在后面写上就可以,有参数写参数,无参数直接是()
^NSInteger {
NSLog(@"匿名block");
return 6;
}();
//匿名block有参数的形式
^NSInteger(NSInteger v){
NSLog(@"匿名参数 -- %ld",v);
return v;
}(5555555555);
3、typedef简化Block(常用的block传值)
Test1ViewController.h
#import <UIKit/UIKit.h>
//
typedef void(^Block8)(void);
@interface Test1ViewController : UIViewController
//
@property (nonatomic ,copy) Block8 block8;
//
-(void)callBlock8;
@end
Test1ViewController.m
//
-(void)callBlock8{
self.block8();
}
ViewController.m
Test1ViewController *test1VC = [[Test1ViewController alloc] init];
//
test1VC.block8 = ^{
NSLog(@"用typedef简化block");
};
//调用
[test1VC callBlock8];
4、typedef声明的block作为参数
Test1ViewController.h
#import <UIKit/UIKit.h>
//block做为参数
typedef void(^handleBlock)(void);
@interface Test1ViewController : UIViewController
//typedef简化的block作为参数
-(void)blockWithNum:(NSInteger)num handleBlock:(handleBlock)handle;
@end
Test1ViewController.m
-(void)blockWithNum:(NSInteger)num handleBlock:(handleBlock)handle{
NSLog(@"block作为参数");
//此方法一定要写,否则调用时不会走代码块中的内容
handle();
}
ViewController.m
//typedef声明的代码块做为方法中的参数
[test1VC blockWithNum:3 handleBlock:^{
NSLog(@"走了吗");
}];
5、匿名Block作为参数
匿名block可以直接在方法里做为参数进行传递
method为方法,冒号后面为匿名block做为参数
method:(returnType (^)(parmaer))block{
这句话一定要写,否则再调用此方法时,不会走代码块中的内容
block(parameter);
}
Test1ViewController.h
-(void)blockWithNoParamer:(NSInteger)num block:(void (^) (NSInteger num))block;
Test1ViewController.m
-(void)blockWithNoParamer:(NSInteger)num block:(void (^)(NSInteger nums))block{
NSLog(@"隐匿block作为参数 - %ld",num);
block(10);
}
ViewController.m
[test1VC blockWithNoParamer:5 block:^(NSInteger num) {
NSLog(@"%ld",num);
}];
6、匿名Block作为返回值
Test1ViewController.h
-(void (^)(NSInteger))blockWithNums:(NSInteger)nums;
Test1ViewController.m
-(void (^)(NSInteger))blockWithNums:(NSInteger)nums{
return ^void (NSInteger num1){
NSLog(@"block作为返回值 - %ld",nums);
};
}
ViewController.m
/**
* block作为返回值时 需要再次调用一次 才能走方法里的内容
**/
block6 = [test1VC blockWithNums:9];
block6(99999);
二、Block截获变量
1、截获变量
Block所在函数中,捕获自动变量,但是不能修改他,不然就是编译错误,但是可以改变全局变量,静态变量,全局静态变量,理解如下:
*不能修改自动变量的值是因为Block捕获的是自动变量的const值,名字一样,不能修改
*可以修改静态变量的值是因为静态变量是属于类的,不是某一个变量,由于Block内部不用调用self指针,所以Block可以调用
*如果修改自动变量,就需要添加__block
***__block保证了栈上和Block内(通常在堆上)可以访问和修改“同一个变量”,将栈上用__block修饰的自动变量封装成一个结构体,让其在堆上创建,以便从栈上或堆上访问和修改同一份数据
__block int val = 10;
static int sVal = 6;
testVal = 10;
void (^block)(void) = ^{
//如果没有添加__block,在这个里面是不可以对val进行修改的 会报编译错误
val = 5;
sVal = 89; //静态变量
testVal = 45; //全局变量
allSVal = 90; //全局静态变量
NSLog(@"val = %d,staticVal = %d,testVal = %ld,allSval = %d",val,sVal,testVal,allSVal);
};
val = 3;
//不加__block之前 打印的结果是10,加了__block之后结果就变成了3 前提是block中没有val = 5这句话 否则就是5
block();
2、截获对象
block捕获OC对象时,不同于基本类型,Block会引起对象的引用计数变化
static NSObject *staticObject = nil;
staticObject = [[NSObject alloc] init];
allObject = [[NSObject alloc] init];
NSObject *object1 = [[NSObject alloc] init];
__block NSObject *blockObject1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
void (^testBlocks)(void) = ^{
NSLog(@"staticObject1 - %ld",CFGetRetainCount((__bridge CFTypeRef)staticObject));
NSLog(@"allObject1 - %ld",CFGetRetainCount((__bridge CFTypeRef)allObject));
NSLog(@"bockeObject1 - %ld",CFGetRetainCount((__bridge CFTypeRef)blockObject1));
NSLog(@"Object1 - %ld",CFGetRetainCount((__bridge CFTypeRef)object1));
NSLog(@"object2 - %ld",CFGetRetainCount((__bridge CFTypeRef)object2));
};
testBlocks();
打印结果
- 最终结果:除了block1和block2的retainCount是3以外,其他的都是1
* 对象的引用计数并不是简单的+1,而是加2,这是由于block在创建的时候在栈上,而在赋值给全局变量的时候,被拷贝到了堆上
* 全局对象和静态对象因为在内存中的位置是确定的,所以即便在block中调用了 对其retainCount也不会有影响
* 对于使用了__block的本地变量,也不会对其retainCount产生影响
三、Block类型
根据Block创建的位置不同,Block有三种类型:
_NSContreateStackBlock:在栈上创建的Block对象
_NSConcreteMallocBlock:在堆上创建的Block对象
_NSConcreteGlobalBlock:全局数据区的Block对象
1、查看Block的类型
通过上面打印的结果可得出,Block是本质上也是一个OC对象,最终继承的是NSObject,它内部也有isa指针,而他的类型,则取决于isa指针,可通过class方法或isa指针查看具体类型
2、如何判断Block类型
(1)全局block储存在全局数据区,在函数栈上创建的block,如果没有截获自动变量,block的结构实例还是会被设置在程序的全局数据区,而非栈上
(2)截获了自动变量的block,是存储在堆上的,而非栈上
(3)没有返回值并且截获变量的block是存储在栈上的
void (^blk)(void) = ^{
NSLog(@"Global Block");
};
#pragma mark - 查看block的类型
-(void)checkBlockType{
//代码展示三种类型
int age = 1;
void (^block1)(void) = ^{
NSLog(@"没有截获自动变量");
};
void (^block2)(void) = ^{
NSLog(@"截获了自动变量:%d",age);
};
NSLog(@"\n%@\n%@\n%@\n%@",[block1 class],[block2 class],[^{
NSLog(@"block3:%d",age);
} class],[blk class]);
}
打印结果
四、Block管理内存
配置在栈上的block,如果其所属的栈作用域结束,该block就会被废弃,对于超出Block作用域仍需使用Block的情况,Block提供了将Block从栈上复制到堆上的方法来解决这种问题,即便Block栈作用域已结束,但被拷贝到堆上的Block还可以继续存在
将栈上的block自动复制到堆上的几种情况
1、调用Block的copy方法
2、将Block作为函数返回值时
3、将Block赋值给__strong修改的变量时
4、向Cocoa框架含有usingBlock的方法或者GCD的API传递Block参数时
基于以上四条,ARC下很少能看到栈类中的block,大多数情况编译器都保证了block是在堆上创建的
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
无论MRC还是ARC,栈空间上的block,不会持有对象;堆空间的block,会持有对象。
block的属性修饰词为什么是copy?
block一旦没有进行copy操作,就不会在堆上
block在堆上,程序员就可以对block做内存管理等操作,可以控制block的生命周期
五、其他
1、使用Clang查看Block源码
(1)cd 文件二级地址
(2)使用命令行进行转换:clang -rewrite-objc xxx.m
如果出现“UIKit/UIKit.h file not found”错误,则使用 clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m
(3)执行完后打开文件二级地址,就会看到里面生成的.cpp文件
2、__block 、__weak 、__strong的使用
__block
在代码块中,可以访问局部变量,但是不能修改局部变量,如果需要修改,则使用__block进行修饰,局部变量的值就可以修改了
关于下划线下划线block关键字在MRC和ARC下的不同
__block在MRC下有两个作用
1. 允许在Block中访问和修改局部变量
2. 禁止Block对所引用的对象进行隐式retain操作
__block在ARC下只有一个作用
3. 允许在Block中访问和修改局部变量
a、MRC情况下,用__block可以消除循环引用。
b、 ARC情况下,必须用弱引用才可以解决循环引用问题,iOS 5之后可以直接使用__weak,之前则只能使__unsafe_unretained了,__unsafe_unretained缺点是指针释放后自己不会置
c、不知道 self 什么时候会被释放,为了保证在block内不会被释放,我们添加__strong。更多的时候需要配合strongSelf使用
d、并不是所有的Block里面的self必须要weak一下,有些情况下是可以直接使用self的,比如调用系统的方法:动画代码块
__weak
一个对象A有Block类型的属性,从而持有这个Block,如果Block的代码块中使用到这个对象A,或者仅仅是用用到A对象的属性,会使Block也持有A对象,导致两者互相持有,不能在作用域结束后正常释放。这时就可以使用__weak修饰,对对象A弱引用打破循环
一般常见的修饰方式:
__weak typeof(self) weakSelf = self
__weak Test1ViewController *wealself = self
Reactive Cocoa中的@weakify(self)
如果使用__block修饰的话 在代码块里就需要将其设置为nil,同时代码块必须调用一次
__block与__weak的区别
__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
__block对象可以在block中被重新赋值,__weak不可以。
__strong
这个修饰符要和__weak配对使用 : __strong typeof(self) strongSelf = weakSelf;
他的作用是 保证block在使用该修饰符修饰的对象前 对象不会被提前释放
常见的场景就是对象是一个局部变量 当仅用__weak修饰的时候 出了作用域对象就会被释放 如果没用__strong修饰 block内部就无法使用它了 因为block仅仅弱引用了这个对象
另一个场景就是虽然我们在对象作用域内调用了block 但是block内部使用对象的时机却超出了作用域 例子就是用dispatch_after的场景
MyClass *obj = [[MyClass alloc] init];
__weak MyClass *weakObj = obj;
NSLog(@"before block retainCount - %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
void (^block)(void) = ^(){
NSLog(@"TestObj对象地址:%@",weakObj);
dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
for (int i = 0; i < 1000000; i++) {
// 模拟一个耗时的任务
}
NSLog(@"耗时的任务 结束 TestObj对象地址:%@",weakObj);
});
};
NSLog(@"after block retainCount - %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
block();
打印结果
使用__strong后
MyClass *obj = [[MyClass alloc] init];
__weak MyClass *weakObj = obj;
NSLog(@"before block retainCount - %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
void (^block)(void) = ^(){
__strong MyClass *strongObj = weakObj;
if(! strongObj) return;
NSLog(@"TestObj对象地址:%@",strongObj);
dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
for (int i = 0; i < 1000000; i++) {
// 模拟一个耗时的任务
}
NSLog(@"耗时的任务 结束 TestObj对象地址:%@",strongObj);
});
};
NSLog(@"after block retainCount - %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
block();
打印结果