一直C++学的也不怎么好,一直在分析一些Demo的时候发现Block被用到的很多,但是都是迷迷糊糊,搞不懂怎么用,有什么好处。看到一篇还好的博文,但是是繁体。有些词也不是很通俗,试着参照改写一下。
Block
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实现
使用Block Pointer的例子
int result = square(5); // 感覺上不就是函数的用法吗?也可以把Block Pointer當成参数傳給一個函数function,比如
void myFuction( int (^mySquare) (int) ); //函数的声明传入一個有以int为参数类型和int为返回类型的Block的参数
调用myFunction的時候就是这样
int (^mySqaure) (int) = ^(int a) {return a*a;};
// 先給好一個已经声明实现的block pointer叫mySquare
myFunction( mySqaure ) ; //把mySquare這個block pointer传myFunction函数或是不用block pointer 直接給一個block 实体,就這样
myFunction( ^(int a) {return a*a} ) ;作为Objective-C 方法的传入参数的話都是要把类型写在变量前面然后加上小括号,因些应该就是这样写
-(void) objcMethod:( int (^) (int) ) square; // square变量的类型是 int (^) (int)至此對Block是基本的认识 接下來我們要说Block有关的用法和特点
首先是來看一下在Block里存取外部变量的方法
存取变量
1. 可以读取和Block pointer同一個范围的变量值:
{
int outA = 8;
int (^myPtr) (int) = ^(int a) {return outA+a;};
int result = myPtr(3); // result is 11再來看一個奇怪的例子
}
{实际上,myPtr在其实现部分用到outA這個变量值的时候是做了一個copy的调用把outA的值copy下來。所以之后outA即使改变了对于myPtr里copy的值是沒有影响的。
int outA = 8;
int (^myPtr) (int) = ^(int a) {return outA+a;};
// block 裡面可以读同一個作用范围的outA的值
outA = 5; // 在调用myPtr之前变更outA的值
int result = myPtr(3); // result 的值是 11并不是 8
}
要注意的是,這個指的值是变量的值,如果這個变量的值是个pointer的話,它指到的值是可以在block里被改变。
{原本mutableArray的值是{@"one",@"two",@"three"}在block里面被更改mutableArray所指向的元素后,mutableArray的值就是{@"one",@"two"}
NSMutableArray * mutableArray = [NSMutableArray arrayWithObjects:@"one",@"two",@"three",nil];
int result = ^(int a) { [mutableArray removeLastObject]; return a*a;} (5);
NSLog(@"test array %@", mutableArray);
}
2. 直接存取static 的变量
{甚至可以在block裡面直接改变outA的值比如這样
static int outA = 8;
int (^myPtr) (int) = ^(int a) {return outA+a;};
outA = 5; // 在呼叫myPtr之前改變outA的值
int result = myPtr(3); // result 的值是 8,因為outA是個static 特性的变量
}
{3. Block Variable
static int outA = 8;
int (^myPtr) (int) = ^(int a) { outA= 5; return outA+a;};
// block 裡面改變outA的值
int result = myPtr(3); // result 的值是 8,
}
在某個变量前面如果加上修飾字 _ _ block 的話(注意block前有兩個下划线),這個变量又叫做block variable(变量)。那在block里就可以任意修改此变量值,变量值的变化也可以知道。
{myPtr和myPtr2都有用到num這個block variable,result的值就會是7
__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);
}
生命周期和内存管理
因为block也是继承自NSObject,所以其生命周期和内存的管理也就非常之重要。
block一开始都是被放到堆里,换句话说其生命周期随着method或function結束就會被回收,和一般变量的生命周期一样。
关于内存管理請遵循这几点
1. block pointer的实现会在method或function結束后就会被清理
2. 如果要保存block pointer要用- copy 指令,这样block pointer就會被放到堆裡
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);代码由genBlock里生成的block再指定給main function的 outBlock变量 ,运行代码會得到
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 ;
}
Segmentation fault
(註:有時候把 genBlock裡的a 去掉就可以跑出結果的情形,這是系統缓冲区内存,並不是inBlock真得一直存在,久了還是會被回收,千萬不要以為是對的寫法)
表示我們用到了不該用的内存,在这个例子的情況下是在genBlock里的 inBlock 变量在return的時候就被回收了, outBlock 无法调用一个合法的内存位置-retainCount就沒意义了。
如果這個時候需要保留inBlock的值就要用-copy指令,將genBlock改成
MyBlock genBlock() {[inBlock copy]的返回值就會被放到栈,就可以一直使用(記得要release)
int a = 3;
MyBlock inBlock = ^(int n) {
return n*a;
};
return [inBlock copy] ;
}
結果是
result is 1
result is 15
再次提醒要記得release outBlock。
如果一返回[inBlock copy]的值就不再需要的時候可以這樣
MyBlock genBlock() {-copy指令是為了要把block 從stack搬到heap,autorelease是為了平衝retainCount加到autorelease oop ,回傳之後等到事件結束就清掉。
int a = 3;
MyBlock inBlock = ^(int n) {
return n*a;
};
return [[inBlock copy] autorelease] ;
}
接下來是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];在main 裡使用如下
[super dealloc];
}
@end
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{在Block主體裡用newTitle這個變數而不是title。這樣self就不會被retain了。
if(self = [super init]){
title = newTitle;
myLog = [^(NSString * deco) { NSLog(@" %@%@%@",deco, newTitle, deco ); } copy];
}
return self;
}
最後談一個小陷井
void (^myLog) (void);
BOOL result ;
if(result)
myLog = ^ {NSLog(@"YES");};
else
myLog = ^ {NSLog(@"NO");};
myLog();
這樣很可能就會當掉了,因為myLog 實體在if 或是else結束後就被清掉了。要記得。
要用copy來解決這個問題,但要記得release。