在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
1、Block基础 可以将Object-c的代码块理解为C++语言的函数指针,通过代码块就能够像对待对象一样,指定要在方法和函数中传递的任意代码部分。 1.1 声明代码块 代码块的声明与函数指针的声明类似,都定义了参数和返回值;不同的是函数指针使用"*",而代码块使用"^"。在声明代码块后,需要给其赋值将要执行内容,给赋值的代码块可以省略返回值类型,因为编译器可以从存储代码块的变量确定返回值类型;但是必须再次在括号中提供代码块的参数说明。
1 void (^myBlock)(NSString *x); //声明代码块变量
2 myBlock = ^(NSString *x) //给代码块变量赋值 3 { 4 NSLog(@”%@”, x); 5 }; 6 myBlock(“hello world”); //运行代码块,类似调用函数 7 void (^myBlock)(NSString *x) = ^(NSString *x) 8 { 9 NSLog(@”%@”, x); 10 } 在一个表达式内可以同时进行代码块变量的声明和初始化。这点也和使用普通的变量一样。你可以先声明变量,之后再初始化,也可以一次完成。 1.2 使用代码块 使用代码块涉及三个方面:调用代码块、代码块作为函数参数、代码块作为方法参数。 1) 调用代码块 执行代码块与执行函数指针一样,都是在调用时传递相应参数,从而即可执行代码块体或函数体。
1 int main(int argc, const char* argv[])
2 { 3 void (^myBlock)(NSString *x); //声明代码块变量 4 myBlock = ^(NSString *x) //给代码块变量赋值 5 { 6 NSLog(@”%@”, x); 7 }; 8 myBlock(“hello world”); //运行代码块,类似调用函数 9 return 0; 10 } 2) 作为函数参数 代码块在函数参数列表中的声明,与正常情况下的声明一样,无任何差别,如下所示:
1 void fBlock( int (^theBlock)(NSString *value) )
2 { 3 int result = theBlock(@”hello”); 4 }
3) 作为方法参数 将代码块作为参数传入到对象或类方法(不是函数的参数)时,语法稍有不同。如下所示。
1 -(void) method: (int (^)(NSString *)) block
2 { 3 int result = block(@”hello”); 4 } 注意: 我们是在代码块定义之后才传入了代码块参数的名称(保存代码块的变量在方法体内使用的名称),因此,通常在代码块定义时需要提供代码块变量名的位置却仅传入了^符号。 1.3 代码块作用域 代码块内程序既可以访问代码块内的局部变量,也可以访问代码块外的变量,但作用域不一样。 1) 本地变量 本地变量就是与代码块在同一范围内声明的变量,即本地变量是在代码块外定义的变量。
1 int main(int argc, const char * argv[])
2 { 3 int a =1,b = 2; 4 int (^mult)(void); //声明代码块指针 5 6 a =10;b = 20; 7 mult=^{ //给代码块指针赋值 8 return a*b; 9 }; 10 11 a = 20; 12 b = 50; 13 14 NSLog(@"%d",mult()); //调用代码块指针 15 16 return 0; 17 } 18 2016-04-25 20:00:05.663 propertyProject[890:61223] 200 因为本地变量是本地的,代码块会在定义时(赋值时)捕获创建点时的状态,即在创建代码块时会复制并保存外部变量的状态(变量值),所以输出的是200,不是2,也不是1000. 2) 全局变量 代码块外部的全局变量(静态变量)是全局的,所以其可以在任何时候被修改,即在创建代码块是不复制全局变量;并且全局变量可以在代码块中进行修改,从而反应到原来全局的变量中。
1 int main(int argc, const char * argv[])
2 { 3 static int a =1,b = 2; 4 int (^mult)(void); //声明代码块指针 5 6 a =10;b = 20; 7 mult=^{ //给代码块指针赋值 8 return a*b; 9 }; 10 11 a = 20; 12 b = 50; 13 14 NSLog(@"%d",mult()); //调用代码块指针 15 16 return 0; 17 } 18 2016-04-25 20:00:05.663 propertyProject[890:61223] 1000 3) __block变量 在创建代码块时会复制本地变量的值,从而将本地变量作为代码块中的局部变量(常量),如果想要修改它们的值,可以将本地变量声明为全局变量(static),或者是添加语言指令__block声明为可读写的。
1 int main(int argc, const char * argv[]) {
2 __block int a =1; //或为声明为:static int a =1 3 int (^mult)(void); //声明代码块指针 4 mult=^{ //给代码块指针赋值 5 a = 22; 6 return a; 7 }; 8 mult(); //调用代码块指针 9 NSLog(@"%d",a); 10 11 return 0; 12 } 13 2016-04-25 20:42:01.208 propertyProject[1034:77740] 22 4) 参数变量 代码块的参数变量与函数的形式参数一样,都只是临时变量,对参数变量修改后,不能反应到原来的变量中。 5) 局部变量 局部变量是指在代码块中的定义的变量,与函数中定义的局部变量类似。 2、内存区域 对于C++对象的存储区域有全局、栈、堆区域,由于Objective-C的block也是一种对象类型,所以其存储区域也有全局、栈和堆区域。 2.1栈区域 类似C/C++语言,在局部区域定义的block也是存储在栈区域,也就是说,block只在其定义的范围有效。如下面这段代码就有危险:
1 void (^block)();
2 if() 3 { 4 block = ^{ 5 NSLog(@"Block A"); 6 }; 7 } 8 else 9 { 10 block = ^{ 11 NSLog(@"Block B"); 12 }; 13 } 14 block(); 定义在if及else语句中的两个块都分配在栈内存中。编译器会给每个块分配好栈内存,然而等离开了相应的范围之后,编译器有可能把分配给块的内存覆盖掉。于是,这两个块只能保证在对应的if或else语句范围内有效。这样写出来的代码可以编译,但是运行起来时而正确,时而错误。若编译器未覆盖待执行的block,则程序正常执行,若覆盖,则程序崩溃。 2.2 堆区域 为解决上述例子中的问题,可将栈中的block拷贝(copy)到堆中,因为堆中的block在任意其它范围也有效。而且一旦复制到堆上,block就成了带引用计数的对象,所以后续的复制操作都不会真的执行复制,之时递增block对象的引用计数。如果不再使用这个block,那就应将其释放,在ARC环境下会自动释放,而手动管理引用计数时,则需要自己来调用release方法。当引用计数降为0后,"分配在堆上的block"会像其它对象一样,被系统回收;而"分配在栈上的block"则无须明确释放,因为栈内存本来就会自动回收。 如下的代码就安全了,但如果是手动管理引用计数,还需进行释放:
1 void (^block)();
2 if(/* DISABLES CODE */ (1)) 3 { 4 block = [^{ 5 NSLog(@"Block A"); 6 } copy]; 7 } 8 else 9 { 10 block = [^{ 11 NSLog(@"Block B"); 12 } copy]; 13 } 14 block(); 2.3 全局区域 全局block不会捕捉任何状态(比如外围的变量等),运行时也无须有状态来参与,block所使用的整个内存区域在编译期已经完全确定了,因此,全局block可以声明在全局内存里,而不需要在每次用到的时候在栈中创建。另外,全局block的拷贝操作是个空操作,因为全局block决不可能为系统所回收。 如下例子,这种block就相当是一个单利:
1 void (^block)() =^{
2 NSLog(@"hello world"); 3 }; 4 5 int main(int argc, const char * argv[]) { 6 @autoreleasepool { 7 block(); 8 } 9 } 由于运行该block所需的全部信息都能在编译期确定,所以可把它做成全局block。这完全是种优化技术:若把如此简单的block当成复杂的block来处理,那就会在复制及丢弃该block时执行一些无谓的操作。 3、使用问题 3.1 内存泄露体现 block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题。一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,简单说就是:
1 self.someBlock = ^(Type var){
2 [self dosomething]; 3 self.otherVar = XXX; 4 或者_otherVar = ... 5 };
如下所示:
1 @interface myClass : NSObject<NSCopying>
2 @property(nullable) NSString* name; 3 @property void (^block)(void); 4 -(void) myMethod; 5 @end 6 @implementation myClass 7 -(void) myMethod 8 { 9 self.block = ^() 10 { 11 self.name = @"hello"; //会被编译器捕捉到并及时提醒:Projects/propertyProject/myClass.m:28:9: Capturing 'self' strongly in this block is likely to lead to a retain cycle 12 }; 13 } 14 @end 注意: 在[3]文章中介绍:在block代码中没有显式地出现"self",也会出现循环引用。但经测试如果不使用self是不会出现循环引用提醒的,即在上述例子中,若使用"_name = @"hello""表达式,是不会有警告的。 3.2 解决办法 1) ARC环境下 ARC环境下可以通过使用__weak来声明一个中间变量,并将self赋值给这个中间变量,而在block中通过这个中间变量来访问self的成员属性,通过这种方式告诉block,不要在block内部对self进行强制strong引用,如下所示的例子编译器将不会出现提醒:
1 -(void) myMethod
2 { 3 __weak typeof(self) weakSelf=self; 4 self.block = ^() 5 { 6 weakSelf.name = @"hello"; 7 }; 8 }
2) MRC环境下 解决方式与上述基本一致,只不过将__weak关键字换成__block即可,这样的意思是告诉block:小子,不要在内部对self进行retain了! 3.3 委托delegate 在委托问题上也容易出现循环引用问题,规避该问题的杀手锏也是简单到哭,一字诀:声明delegate时请用assign(MRC)或者weak(ARC),千万别手贱玩一下retain或者strong,毕竟这基本逃不掉循环引用了! 4、参考文献 [1] objective-C基础教程 [2] Effective Objective-C 2.0 [3] Block以及对应的使用方法 [5] iOS中block实现的探究 |
请发表评论