不会使用Block的iOS程序员,不是一个合格的程序员
Block没有你想象中的那么难,不要害怕,不要畏惧,勇敢尝试
Block进阶:
Block其实就是一个代码块,把你想要执行的代码封装在这个代码块里,等到需要的时候再去调用。
个人觉得Block优势如下:第一可以使代码看起来更简单明了,第二可以取代以前的delegate使代码的逻辑看起来更清晰。
Block代码块和普通函数都是一段代码,两者有什么区别?
苹果官网Block文档是这样描述的:
Block代码:是一个函数对象,是在程序运行过程中产生的;
普通函数:是一段固定代码,产生于编译期;
借一张图表达基本定义:
完整定义如下:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
注1: Block的声明与赋值只是保存了一段代码段,必须调用才能执行内部代码
注2: ^被称作"脱字符"
void (^myBlock1)(void); //无返回值,无参数 void (^myBlock2)(NSObject, int); //无返回值,有参数 NSString* (^myBlock3)(NSString* name, int age); //有返回值和参数,并且在参数类型后面加入了参数名(仅为可读性)
Block变量的声明
Block变量的声明格式为: 返回值类型(^Block名字)(参数列表);
注3:形参变量名称可以省略,只留有变量类型即可
// 声明一个无返回值,参数为两个字符串对象,叫做aBlock的Block
void(^aBlock)(NSString *x, NSString *y);
// 形参变量名称可以省略,只留有变量类型即可
void(^aBlock)(NSString *, NSString *);
巧记Block格式
很多人觉得Block格式定义很难记,其实我们可以通过与 java 函数方法对比加强记忆:
int getResult (String a, String b) // Java的方法声明
int (^MyBlockName) (String a, String b) // iOS的block声明
block的定义
/*定义属性,block属性可以用strong修饰,也可以用copy修饰 */
@property (nonatomic, strong) void(^myBlock)(); //无参无返回值
@property (nonatomic, strong) void(^myBlock1)(NSString *); //带参数无返回值
@property (nonatomic, strong) NSString *(^myBlock2)(NSString *); //带参数与返回值
//定义变量
void(^myBlock)() = nil; //无参无返回值
void(^myBlock1)(NSString *) = nil; //带参数无返回值
NSString *(^myBlock2)(NSString *) = nil; //带参数与返回值
block被当做方法的参数
格式:(block类型)参数名称
- (void)test:(void(^)())testBlock //无参无返回值
- (void)test1:(void(^)(NSString *))testBlock //带参数无返回值
- (void)test2:(NSString *(^)(NSString *))testBlock //带参数与返回值
使用typedef定义block
typedef void(^myBlock)(); //以后就可以使用myBlock定义无参无返回值的block
typedef void(^myBlock1)(NSString *); //使用myBlock1定义参数类型为NSString的block
typedef NSString *(^myBlock2)(NSString *); //使用myBlock2定义参数类型为NSString,返回值也为NSString的block
//定义属性
@property (nonatomic, strong) myBlock testBlock;
//定义变量
myBlock testBlock = nil;
//当做参数
- (void)test:(myBlock)testBlock;
以下两者等价:
- (void) testAnimations:(void (^) (void) ) animations; // 无参数
- (void) testAnimations:(void (^) () ) animations; // 无参数
block的赋值
格式:block = ^返回值类型 (参数列表) {函数主体}
注4: 通常情况下,可以省略返回值类型,因为编译器可以从存储代码块的变量中确定返回值的类型。
没有参数没有返回值
myBlock testBlock = ^void(){
NSLog(@"test");
};
没有返回值,void可以省略
myBlock testBlock1 = ^(){
NSLog(@"test1");
};
没有参数,小括号也可以省略
myBlock testBlock2 = ^{
NSLog(@"test2");
};
有参数没有返回值
myBlock1 testBlock = ^void(NSString *str) {
NSLog(str);
}
省略void
myBlock1 testBlock = ^(NSString *str) {
NSLog(str);
}
有参数有返回值
myBlock2 testBlock = ^NSString *(NSString *str) {
NSLog(str)
return @"hi";
}
有返回值时也可以省略返回值类型
myBlock2 testBlock2 = ^(NSString *str) {
NSLog(str)
return @"hi";
}
声明Block变量的同时进行赋值
int(^myBlock)(int) = ^(int num){
return num * 7;
};
// 如果没有参数列表,在赋值时参数列表可以省略
void(^aVoidBlock)() = ^{
NSLog(@"I am a aVoidBlock");
};
注5:如果没有参数,= 左边用括号表示,= 右边参数可以省略
Block变量的调用
// 调用后控制台输出"Li Lei love Han Meimei"
aBlock(@"Li Lei",@"Han Meimei");
// 调用后控制台输出"result = 63"
NSLog(@"result = %d", myBlock(9));
// 调用后控制台输出"I am a aVoidBlock"
aVoidBlock();
Block作为OC函数参数
// 1.定义一个形参为Block的OC函数
- (void)useBlockForOC:(int(^)(int, int))aBlock
{
NSLog(@"result = %d", aBlock(300,200));
}
// 2.声明并赋值定义一个Block变量
int(^addBlock)(int, int) = ^(int x, int y){
return x+y;
};
// 3.以Block作为函数参数,把Block像对象一样传递
[self useBlockForOC:addBlock];
// 将第2点和第3点合并一起,以内联定义的Block作为函数参数
[self useBlockForOC:^(int x, int y){
return x+y;
}];
注意:当block作为方法参数时,定义(形参)和使用(实参)格式不一样。
// 定义block作为参数
+ (RACSignal *)createSignal:(RACDisposable * (^) (id<RACSubscriber> subscriber) )didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
// 使用block作为参数
RACSignal *signal =
[RACSignal createSignal:^ RACDisposable * ( id<RACSubscriber> subscriber) {
[subscriber sendNext:letterSubject];
[subscriber sendNext:numberSubject];
[subscriber sendCompleted];
return nil; // 如果不为空,返回类型是 RACDisposable *
}];
Block在定义时并不会执行内部的代码,只有在调用时候才会执行。
使用示例:
// 在AAViewController.h定义
@property (nonatomic, copy) void (^successBlock)(NSInteger count);
// 在AAViewController.m赋值
if (self.successBlock && !_willUpdate){
self.successBlock([self.cards count]);
}
在BViewController.m中调用:
AAViewController *aa=[[[AAViewController alloc] init];
// 回调要如何处理
aa.successBlock=^(NSInteger count) {
if (count==0) {
// 处理代码
}
};
[aa httpRequest];
利用typedef为Block进行重命名
我们可以使用typedef为block进行一次重命名,方法跟为函数指针进行重命名是一样的:typedef int (^Sum) (int, int);
这样我们就利用typedef定义了一个block,这个block的名字就是Sum,需要传入两个参数。当我们需要使用时,就可以这样做了:
Sum mysum = ^(int a, int b) {
n = 2;
return (a + b)*n;
};
这样就完整的定义好了一个block了,接下来的使用如下:
#import <Foundation/Foundation.h>
typedef int (^Sum) (int, int);
int main(int argc, const char * argv[])
{
__block int n = 1;
@autoreleasepool {
Sum mysum = ^(int a, int b) {
n = 2;
return (a + b)*n;
};
NSLog(@"(3 + 5) * %i = %d", n, mysum(3, 5));
}
return 0;
}
Block在内存中的位置
根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。NSGlobalBlock:类似函数,位于text段;
NSStackBlock:位于栈内存,函数返回后Block将无效;
NSMallocBlock:位于堆内存。
示例1:
BlkSum blk1 = ^ long (int a, int b) {
return a + b;
};
NSLog(@"blk1 = %@", blk1);// 打印结果:blk1 = <__NSGlobalBlock__: 0x47d0>
示例2:
int base = 100;
BlkSum blk2 = ^ long (int a, int b) {
return base + a + b;
};
NSLog(@"blk2 = %@", blk2); // 打印结果:blk2 = <__NSStackBlock__: 0xbfffddf8>
示例3:
BlkSum blk3 = [[blk2 copy] autorelease];
NSLog(@"blk3 = %@", blk3); // 打印结果:blk3 = <__NSMallocBlock__: 0x902fda0>
blk1和blk2的区别在于:
blk1没有使用Block以外的任何外部变量,Block不需要建立局部变量值的快照,这使blk1与一般函数没有任何区别。
blk2与blk1唯一不同是的使用了局部变量base,
注意1:在定义(注意是“定义”,不是“运行”)blk2时,局部变量base当前值被copy到栈上,作为常量供Block使用。执行下面代码,结果是203,而不是204。
int base = 100;
base += 100;
BlkSum sum = ^ long (int a, int b) {
return base + a + b;
};
base++;
printf("%ld",sum(1,2));
在Block内变量base是只读的,如果想在Block内改变base的值,在定义base时要用 __block修饰:__block int base = 100;
__block int base = 100;
base += 100;
BlkSum sum = ^ long (int a, int b) {
base += 10;
return base + a + b;
};
base++;
printf("%ld\n",sum(1,2));
printf("%d\n",base);
输出将是214,211。
注意2:Block中使用__block修饰的变量时,将取变量此刻运行时的值,而不是定义时的快照。这个例子中,执行sum(1,2)时,base将取base++之后的值,也就是201,再执行Blockbase+=10; base+a+b,运行结果是214。执行完Block时,base已经变成211了。
局部自动变量:在Block中只读。Block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。
int base = 100;
BlkSum sum = ^ long (int a, int b) {
// base++; 编译错误,只读
return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2)); // 这里输出是103,而不是3
static变量、全局变量。如果把上个例子的base改成全局的、或static。Block就可以对他进行读写了。因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是 直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量。
static修饰变量,效果与_ _block一样
static int base = 100;
BlkSum sum = ^ long (int a, int b) {
base++;
return base + a + b;
};
base = 0;
printf("%d\n", base);
printf("%ld\n",sum(1,2)); // 这里输出是3,而不是103
printf("%d\n", base);
输出结果是:
0
4
1
表明Block外部对base的更新会影响Block中的base的取值,同样Block对base的更新也会影响Block外部的base值。
Block变量,被__block修饰的变量称作Block变量。基本类型的Block变量等效于全局变量、或静态变量。
retain cycle
retain cycle问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。比如:@implementation TsetBlock
-(id)init{
if (self = [superinit]) {
self.testStr =@"中国";
self.block = ^(NSString *name, NSString *str){
NSLog(@"arr:%@",self.testStr); // 编译警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle
};
}
returnself;
}
@end
网上大部分帖子都表述为"block里面引用了self导致循环引用",其实这种说法是不严谨的, 不一定要显式地出现"self"字眼才会引起循环引用。我们改一下代码,不通过属性self.testStr去访问String变量,而是通过实例变量_testStr去访问,如下:
@implementation TsetBlock
-(id)init{
if (self = [superinit]) {
self.testStr =@"中国";
self.block = ^(NSString *name,NSString *str){
NSLog(@"arr:%@", _testStr); // 同样出现: Capturing 'self' strongly in this block is likely to lead to a retain cycle
};
}
returnself;
}
@end
即使在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!
要分两种环境去解决:在ARC下不用__block ,而是用 __weak 为了避免出现循环引用
1.ARC:用__week
__weaktypeof (self) weakSelf = self; 或者
__weak someClass *weakSelf = self;
2.MRC:用__block ,__block修饰的变量在Block copy时是不会retain的,所以,也可以做到破解循环引用。
__block someClass *blockSelf = self;
使用如下代码解决循环引用
weakify(self);
success:^(AFHTTPRequestOperation *operation, id responseObject) {
strongify(self);
if (!self__weak_) return ;
//...................
}
1、weakify(self); 创建一个指向self的弱引用
2、strongify(self); 当加上修饰符strong时,当别处把“self”释放掉,但调用该“self”的block如果仍然没有执行结束,那么系统就会等待block执行完成后再释放,对该“self”在block中的使用起到了保护作用。当block执行结束后会自动释放掉。
3、if (!self__weak_) return ; 进行判断,如果在执行strongify(self)之前“self已经被释放掉了,则此时self=nil,所以直接return即可”
Block的copy、retain、release操作
对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;NSGlobalBlock:retain、copy、release操作都无效;
NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。
NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
尽量不要对Block使用retain操作。
几个应用实例:
1、代码用在字符串数组排序
NSArray *stringArray = [NSArray arrayWithObjects:@"abc 1", @"abc 21", @"abc 12",@"abc 13",@"abc 05",nil];
NSComparator sortBlock = ^(id string1, id string2)
{
return [string1 compare:string2];
};
NSArray *sortArray = [stringArray sortedArrayUsingComparator:sortBlock];
NSLog(@"sortArray:%@", sortArray);
运行结果:sortArray:(
)
2、代码块的递归调用
代码块想要递归调用,代码块变量必须是全局变量或者是静态变量,这样在程序启动的时候代码块变量就初始化了,可以递归调用
- static
void (^ const blocks)(int) = ^(int i) - {
-
if (i > 0) { -
NSLog(@"num:%d", i); -
blocks(i - 1); -
} - };
- blocks(3);
num:3
num:2
num:1
参考资料源自互联网