Objective-C中的Block
来源:互联网 发布:气动打标机软件光盘 编辑:程序博客网 时间:2024/06/10 05:47
技术是需要沉淀的。接触iOS开发也有大半年时间了,从一开始的纯白到现在自我感觉略懂一点,其实进步是明显的。无数牛人表示技术博是完成菜鸟到高手蜕变的途径之一,虽然这个博客并非是为技术而生,但是也许作为工科背景下的我来说,每天都写文艺的东西显然并不现实。于是就有了这个集子:能工巧匠集。用这篇开篇,
写一些在开发过程中的积累和感悟,大部分应该是Objectiv-C和XCode的内容,包括基本语法特性和小技巧,或者自己喜欢的一些开源代码的用法分析等等。也许以后会扩展到Unity3D或者UDK的一些3D引擎的心得,当然也有可能会有别的一些自己认为值得分享的东西。这个集子的目的,一来是记录自己一步一步成长的脚印,二来也算是为新来者铺一条直一点的道路。集子里的东西仅仅是自己的心得体会,高手路过请一笑置之..感恩。
iOS SDK 4.0开始,Apple引入了block这一特性,而自从block特性诞生之日起,似乎它就受到了Apple特殊的照顾和青睐。字面上说,block就是一个代码块,但是它的神奇之处在于在内联(inline)执行的时候(这和C++很像)还可以传递参数。同时block本身也可以被作为参数在方法和函数间传递,这就给予了block无限的可能。
先来看一个简单的block吧:
// Defining a block variableBOOL (^isInputEven)(int) = ^(int input){ if (input % 2 == 0) return YES; else return NO;};
以上定义了一个block变量,block本身是一个程序段,因此有返回值,这里这个block返回的类型为BOOL。”^”符号表示开始定义,block的名称紧跟在^符号之后,为isInputEven,也即以后使用inline方式调用该block时所需要的名称。这段block接受一个int型的参数,而在等号后面的int input是对传入的int类型的说明,在该block内,该int的局部变量名为input。
调用这个block非常简单,类似调用c函数的方式即可:
// Call similar to a C function callint x = -101;NSLog(@"%d %@ number", x, isInputEven(x) ? @"is an even" : @"is not an even");
不出意外的话输出为-101 is not an even number
以上的用法没有什么特别之处,只不过是简单的内联函数罢了。但是block的神奇之处在于block外的变量可以无缝地直接在block内部使用,比如这样:
float price = 1.99;float (^finalPrice)(int) = ^(int quantity){ // Notice local variable price is // accessible in the block return quantity * price;};int orderQuantity = 10;NSLog(@"Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity));
输出为Ordering 10 units, final price is: $19.90
相当开心,block外的price成功地传递到了block内部,这意味着内联函数可以使用本地变量,但是需要注意的是,你不能在block内部改变本地变量的值,比如在^{}里写price = 0.99这样的语句的话,你亲爱的compiler一定是会complain的。而更需要注意的是price这样的局部变量的变化是不会体现在block里的!比如接着上面写成这样的话:
price = .99;NSLog(@"Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity));
输出还是Ordering 10 units, final price is: $19.90,可以理解为在block内局部变量price是readonly的。如果确实需要传递给block变量值的话,可以考虑下面两种方法:
1、将局部变量声明为__block,表示将会由block进行操作,比如:
// Use the __block storage modifier to allow changes to 'price'__block float price = 1.99;float (^finalPrice)(int) = ^(int quantity){ return quantity * price;};int orderQuantity = 10;price = .99;NSLog(@"With block storage modifier - Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity));
此时输出为With block storage modifier – Ordering 10 units, final price is: $9.90
2、使用实例变量——这个比较没什么好说的,实例内的变量横行于整个实例内..可谓霸道无敌…=_=
block外的对象和基本数据一样,也可以作为block的参数。而让人开心的是,block将自动retain传递进来的参数,而不需担心在block执行之前局部对象变量已经被释放的问题。这里就不深究这个问题了,只要严格遵循Apple的thread safe来写,block的内存管理并不存在问题。
由于block的灵活的机制,导致iOS SDK 4.0开始,Apple大力提倡在各种地方应用block机制。最典型的当属UIView的动画了:在4.0前写一个UIView的Animation大概是这样的:
[UIView beginAnimations:@"ToggleSiblings"context:nil];[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:YES];[UIViewsetAnimationDuration:1.0];// Make your changes[UIView commitAnimations];
在一个不知名的小角落里的begin/commit两行代码间写下需要进行的动作,然后静待发生。而4.0后这样的方法直接被discouraged了,取而代之的正是block:
[UIView animateWithDuration:5.0animations:^{ view.opacity = 0.5;}];
简单明了,一切就这么发生了..
可能有人会觉得block的语法很奇怪,不像是OOP的风格,诚然直接使用的block看起来破坏了OOP的结构,也让实例的内存管理出现了某些“看上去奇怪”的现象。但是通过typedef的方法,可以将block进行简单包装,让它的behaviour更靠近对象一些:
- typedef double (^unary_operation_t)(double op);
定义了一个接受一个double型作为变量,类型为unary_operation_t的block,之后在使用前用类似C的语法声明一个unary_operation_t类型的”实例”,并且定义内容后便可以直接使用这个block了~
unary_operation_t square;square = ^(double operand) { return operand * operand;}
啰嗦一句的还是内存管理的问题,block始终不是对象,而block的内存管理自然也是和普通对象不一样。系统会为block在堆上分配内存,而当把block当做对象进行处理时(比如将其压入一个NSMutableArray),我们需要获取它的一份copy(比如[square copy]),并且在Array retain了这个block后将其释放([square autorelease]是不错的选择)。而对于block本身和调用该block的实例,则可以放心:SDK会将调用block的实例自动retain,直至block执行完毕后再对实例release,因此不会出现block执行到一半,实例就被dealloc这样的尴尬的局面。
iOS SDK 4.0以后,随着block的加入很多特性也随之添加或者发生了升级。Apple所推荐的block使用范围包括以下几个方面:
- 枚举——通过block获取枚举对象或控制枚举进程
- View动画——简单明了的方式规定动画
- 排序——在block内写排序算法
- 通知——当某事件发生后执行block内的代码
- 错误处理——当错误发生时执行block代码
- 完成处理——当方法执行完毕后执行block代码
- GCD多线程——多线程控制,关于这个以后有机会再写…
仔细研读4.0的SDK的话,会发现很多常用类中都加入了不少带block作为参数的方法,改变固有思维习惯,适应并尽可能利用block给程序上带来的便捷,无疑是提高效率和使代码优雅的很好的途径~
Objective-C block详解
Apple 在C, Objective-C, C++加上Block这个延申用法。目前只有Mac 10.6 和iOS 4有支援。Block是由一堆可执行的程式组成,也可以称做没有名字的Function (Anonymous function)。如果是Mac 10.6 或 iOS 4.0 之前的平台可以利用 http://code.google.com/p/plblocks/ 这个project得以支援Block语法。 Apple有一个叫做GCD(Grand Central Dispach)的新功能,用在同步处理(concurrency)的环境下有更好的效率。Block语法产生的动机就是来自于GCD,用Block包好 一个工作量交给GCD,GCD有一个宏观的视野可以来分配CPU,GPU,Memory的来下最好的决定。
Block 简介
Block其实行为和Function很像,最大的差别是在可以存取同一个Scope的变数值。 Block 实体会长成这样
^(传入参数列) {行为主体};
Block实体开头是"^",接着是由小括号所包起来的参数列(比如 int a, int b, float c),行为的主体由大括号包起来,专有名词叫做block literal。行为主体可以用return回传值,型别会被compiler自动办识出来。如果没有参数列要这样写(void)。 看个列子
^(int a) {return a*a;};
这是代表Block会回传输入值的平方值(int a 就是参数列,return a*a; 就是行为主体)。记得主体里最后要加";"因为是叙述,而整个{}最后也要要加";"因为Block是个物件实体。 用法就是
int result = ^(int a) {return a*a;} (5);
很怪吧。后面小括号里的5 会被当成a的输入值然后经由Block输出5*5 = 25指定给result这个变数。 有没有简单一点的方法不然每次都要写这么长?有。接下来要介绍一个叫Block Pointer的东西来简化我们的写法。 Block Pointer是这样宣告的
回传值 (^名字) (参数列);
直接来看一个列子
int (^square) (int);
// 有一个叫square的Block Pointer,其所指向的Block是有一个int 输入和 int 输出
square = ^(int a ) {return a*a ;}; // 将刚刚Block 实体指定给 square
使用Block Pointer的例子
int result = square(5); // 感觉上不就是funtion的用法吗?
也可以把Block Pointer当成参数传给一个function,比如说
void myFuction( int (^mySquare) (int) ); // function 的宣告,
传入一个有一个int输入和int输出的Block 型别的参数 呼叫这个myFunction的时候就是这样呼叫
int (^mySqaure) (int) = ^(int a) {return a*a;};
// 先给好一个有实体的block pointer叫mySquare
myFunction( mySqaure ) ; //把mySquare这个block pointer给myFunction这个function
或是不用block pointer 直接给一个block 实体,就这样写
myFunction( ^(int a) {return a*a} ) ;
当成Objective-C method 的传入值的话都是要把型别写在变数前面然后加上小括号,因些应该就要这样写
-(void) objcMethod:( int (^) (int) ) square; // square 变数的型别是 int (^) (int)
读文至此是不是对Block有基本的认识? 接下来我们要谈谈Block相关的行为和特色 首先是来看一下在Block里面存取外部变数的方法
存取变数
1. 可以读取和Block pointer同一个scope的变数值:
{
int outA = 8;
int (^myPtr) (int) = ^(int a) {return outA+a;};
// block 里面可以读同一个scope的outA的值
int result = myPtr(3); // result is 11
}
我们再来看一个很有趣的例子
{
int outA = 8;
int (^myPtr) (int) = ^(int a) {return outA+a;};
// block 里面可以读同一个scope的outA的值
outA = 5; // 在呼叫myPtr之前改变outA的值
int result = myPtr(3); // result 的值还是 11并不是 8
}
事实上呢,myPtr在其主体用到outA这个变数值的时候是做了一个copy的动作把outA的值copy下来。所以之后outA即使换了新的值对于myPtr里copy的值是没有影响到的。 要注意的是,这个指的值是变数的值,如果这个变数的值是一个记忆体的位置,换句话说,这个变数是个pointer的话,它指到的值是可以在block里被改变的。
{
NSMutableArray * mutableArray = [NSMutableArray arrayWithObjects:@"one",@"two",@"three",nil];
int result = ^(int a) { [mutableArray removeLastObject]; return a*a;} (5);
NSLog(@"test array %@", mutableArray);
}
原本mutableArray的值是{@"one",@"two",@"three"}在block里被更改mutableArray所指向的物件后,mutableArray的值就会被成{@"one",@"two"} 2. 直接存取static 的变数
{
static int outA = 8;
int (^myPtr) (int) = ^(int a) {return outA+a;};
// block 里面可以读同一个scope的outA的值
outA = 5; // 在呼叫myPtr之前改变outA的值
int result = myPtr(3); // result 的值是 8,因为outA是个static 变数会直接反应其值
}
甚至可以在block里面直接改变outA的值比如这样写
{
static int outA = 8;
int (^myPtr) (int) = ^(int a) { outA= 5; return outA+a;};
// block 里面改变outA的值
int result = myPtr(3); // result 的值是 8,因为outA是个static 变数会直接反应其值
}
3. Block Variable 在某个变数前面如果加上修饰字__block 的话(注意block前有两个下底线),这个变数又称为block variable。那么在block里就可以任意修改此变数值,变数值的改变也可以知道。
{
__block int num = 5;
int (^myPtr) (int) = ^(int a) { return num++;};
int (^myPtr2) (int) = ^(int a) { return num++;};
int result = myPtr(0);
result = myPtr2(0);
}
因为myPtr和myPtr2都有用到num这个block variable,最后result的值就会是7
生命周期和记忆体管理
因为block也是继承自NSObject,所以其生命周期和记忆体的管理也就非常之重要。 block一开始都是被放到stack里,换句话说其生命周期随着method或function结束就会被回收,和一般变数的生命周期一样。 关于记忆体的管理请遵循这几个要点 1. block pointer的实体会在method或function结束后就会被清掉 2. 如果要保存block pointer的实体要用-copy指令,这样block pointer就会被放到heap里 2.1 block 主体里用到的block variable 也会被搬到heap 而有新的记忆体位置,且一并更新有用到这个block variable 的block都指到新的位置 2.2 一般的variable值会被copy 2.3 如果主体里用到的variable是object的话,此object会被retain, block release时也会被release 2.4 __block variable 里用到的object是不会被retain的 首先来看一下这个例子
typedef int (^MyBlock)(int);
MyBlock genBlock();
int main(){
MyBlock outBlock = genBlock();
int result = outBlock(5);
NSLog(@"result is %d",[outBlock retainCount] ); // segmentation fault
NSLog(@"result is %d",result );
return 0 ;
}
MyBlock genBlock() {
int a = 3;
MyBlock inBlock = ^(int n) {
return n*a;
};
return inBlock ;
}
此程式由genBlock里产生的block再指定给main function的outBlock变数,执行这个程式会得到 Segmentation fault (注:有时候把 genBlock里的a 去掉就可以跑出结果的情形,这是系统cache住记忆体,并不是inBlock真得一直存在,久了还是会被回收,千万不要以为是对的写法) 表示我们用到了不该用的记忆体,在这个例子的情况下是在genBlock里的inBlock变数在return的时候就被回收了,outBlock无法有一个合法的记忆体位置-retainCount就没意义了。 如果这个时候需要保留inBlock的值就要用-copy指令,将genBlock改成
MyBlock genBlock() {
int a = 3;
MyBlock inBlock = ^(int n) {
return n*a;
};
return [inBlock copy] ;
}
这样[inBlock copy]的回传值就会被放到heap,就可以一直使用(记得要release) 执行结果是 result is 1 result is 15 再次提醒要记得release outBlock。 如果一回传[inBlock copy]的值就不再需要的时候可以这样写
MyBlock genBlock() {
int a = 3;
MyBlock inBlock = ^(int n) {
return n*a;
};
return [[inBlock copy] autorelease] ;
}
-copy指令是为了要把block 从stack搬到heap,autorelease是为了平冲retainCount加到autorelease oop ,回传之后等到事件结束就清掉。 接下来是block存取到的local variable是个物件的型别,然后做copy 指令时
MyBlock genBlock() {
int a = 3;
NSMutableString * myString = [NSMutableString string];
MyBlock inBlock = ^(int n) {
NSLog(@"retain count of string %d",[myString retainCount]);
return n*a;
};
return [inBlock copy] ;
}
结果会印出 retain count of string 2 这个结果和上面2.3提到的一样,local variable被retain了 那再来试试2.4,在local variable前面加上__block
MyBlock genBlock() {
int a = 3;
__block NSMutableString * myString = [NSMutableString string];
MyBlock inBlock = ^(int n) {
NSLog(@"retain count of string %d",[myString retainCount]);
return n*a;
};
return [inBlock copy] ;
}
执行的结果就是会 retain count of string 1
Block Copying注意事项
如果在Class method里面做copying block动作的话 1. 在Block里如果有直接存取到self,则self会被retain 2. 在Block里如果取存到instance variable (无论直接或是从accessor),则self会被retain 3. 取存到local variable所拥有的object时,这个object会被retain 让我们来看一个自订的Class
@interface MyObject : NSObject {
NSString * title;
void (^myLog) (NSString * deco);
}
-(void) logName;
@end
@implementation MyObject
-(id) initWithTitle:(NSString * ) newTitle{
if(self = [super init]){
title = newTitle;
myLog = [^(NSString * deco) { NSLog(@"%@%@%@",deco, title, deco );} copy];
}
return self;
}
-(void) logName{
myLog(@"==");
}
-(void ) dealloc{
[myLog release];
[title release];
[super dealloc];
}
@end
在main 里使用如下 MyObject * mObj = [[MyObject alloc] initWithTitle:@"Car"]; NSLog(@"retainCount of MyObject is %d",[mObj retainCount] ); [mObj logName]; 其执行的结果为 retainCount of MyObject is 2 ==Car== 因为在MyObject的建构子里myLog这个block pointer用了title这个instance variable然后就会retain self也就是MyObject的物件。 尽量不要这样写,会造成retain cycle,改善的方法是把建构子改成这样
-(id) initWithTitle:(NSString * ) newTitle{
if(self = [super init]){
title = newTitle;
myLog = [^(NSString * deco) { NSLog(@"%@%@%@",deco, newTitle, deco );} copy];
}
return self;
}
在Block主体里用newTitle这个变数而不是title。这样self就不会被retain了。 最后谈一个小陷井 void (^myLog) (void); BOOL result ; if(result) myLog = ^ {NSLog(@"YES");}; else myLog = ^ {NSLog(@"NO");}; myLog(); 这样很可能就会当掉了,因为myLog 实体在if 或是else结束后就被清掉了。要记得。 要用copy来解决这个问题,但要记得release。
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- objective-c中的block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Objective-C中的Block
- Android(SpreadTurm) 通讯录搜索结果分组错误
- SendMessage 和 PostMessage 的区别
- ios开发:NSKeyedUnarchiver,数据序列化,本地存储
- hibernate 如何设置打印sql语句
- Incredibly Handy: Executes native2ascii automatically in STS's jBoss Tools Properties Editor
- Objective-C中的Block
- 汉诺塔
- 浏览器 cookie 详解
- 13-5 初级银行系统(1)
- 720p、1080i、1080p,三种格式(片源),谁更清晰?
- ubuntu终端快捷键
- 系统日志函数syslog(),openlog(),closelog()
- 【Alsa】播放声音和录音详细流程
- Linux学习之--linux路径的细节讨论