iOS之Block代码块的定义及使用
不会使用Block的iOS程序员,不是一个合格的程序员
Block没有你想象中的那么难,不要害怕,不要畏惧,勇敢尝试
Block进阶:
点击打开链接1
点击打开链接2
点击打开链接3
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 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 ",sum(1,2)); printf("%d ",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 ",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 ", base); printf("%ld ",sum(1,2)); // 这里输出是3,而不是103 printf("%d ", 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:(
"abc 05",
"abc 1",
"abc 12",
"abc 13",
"abc 21"
)
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
参考资料源自互联网
- 上一篇: Java反射之getDeclaredField和getField的区别
- 下一篇: Java之可变参数