ObjectiveC中的block用法解析

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 实体会长成这样

[objc] view plaincopy

  1. ^(传入参数列) {行为主体};

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 = ^(inta) {return a*a;} (5);

很怪吧。后面小括号里的5 会被当成a的输入值然后经由Block输出5*5 = 25指定给result这个变数。
有没有简单一点的方法不然每次都要写这么长?有。接下来要介绍一个叫Block Pointer的东西来简化我们的写法。
Block Pointer是这样宣告的

回传值 (^名字) (参数列);

直接来看一个列子

int (^square)(int);

// 有一个叫squareBlock 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(  ^(inta) {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, blockrelease时也会被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改成

MyBlockgenBlock() {
        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]的值就不再需要的时候可以这样写

MyBlockgenBlock() {
        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",[mObjretainCount]  );
 [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。

时间: 2024-11-07 22:51:43

ObjectiveC中的block用法解析的相关文章

objective-c 中随机数的用法 (3种:arc4random() 、random()、CCRANDOM_0_1() )

1.随机数的使用      1).arc4random() 比较精确不需要生成随即种子 使用方法 : 通过arc4random() 获取0到x-1之间的整数的代码如下: int value = arc4random() % x; 获取1到x之间的整数的代码如下: int value = (arc4random() % x) + 1; 2).CCRANDOM_0_1() cocos2d中使用 ,范围是[0,1] 使用方法: float random = CCRANDOM_0_1() * 5; //

Objective-c中的Block(块)详解

Block初探 在Objective-c中NSArray是很常用的容器之一,很多时候我们需要对数组中的数据进行排序,因此与下面类似的代码会经常碰到: NSArray *sortedArray = [array sortedArrayUsingComparator: ^(id obj1, id obj2) { if ([obj1 integerValue] > [obj2 integerValue]) { return (NSComparisonResult)NSOrderedDescending

从C语言的变量声明到Objective-C中的Block语法转载]

原文:From C Declarators to Objective-C Blocks Syntax 作者:Nils Hayat 译者:CocoaChina--sunshine 在这篇文章中,从简单的C语言中各种声明开始,以及复杂的声明组合,到最后Objective-C中的代码块bokck的语法. 花一些时间去了解代码块(block)衍生和组织形式,一旦明白了这些,就可以很方便的声明和使用它,而不用每次需要的时候才去Google一下. 如果你想把能想到的东西用block声明表现出来,请继续阅读!

Objective-C中的Block(闭包)

学习OC有接触到一个新词Block(个人感觉又是一个牛气冲天的词),但不是新的概念,不是新的东西.学过Javascript的小伙伴对闭包应该不陌生吧~学过PHP的应该也不陌生,在PHP5.3版本以后也支持闭包, 也就是OC中所提到的Block. 到底什么是闭包或者block呢?用大白话说就是匿名函数,也就是在函数中可以包含这函数.就是在函数中可以定义匿名函数然后在函数中调用.学习OC中的block之前也小担心一下,Block在OC中属于高级的部分,心里有又有个疑问:学起来难不难?看过Block的

MATLAB中冒号的用法解析

MATLAB中冒号的用法解析 1.: 表示所有的意思. (1)如:a(1,:) 表示a的第1行,示例: 结果: 同样的如果a(2,:)表示a的第2行 (2)反过来,a(:,2) 表示a的第3列,示例: 结果: (3)如果不写数字即a( : ),则表示全部元素,并且返回的结果a变为列向量.示例: 结果: 2.: 表示到,表示一个范围. (1)如a(1:2,3) 表示第1行到第2行的第3列元素.示例: 结果: (2)a(1:2,:) 表示第1行到第2行的所有元素.示例: 结果: (3)同样有a(:,

Objective-C中的Block

1.相关概念 在这篇笔记开始之前,我们需要对以下概念有所了解. 1.1 操作系统中的栈和堆 注:这里所说的堆和栈与数据结构中的堆和栈不是一回事. 我们先来看看一个由C/C++/OBJC编译的程序占用内存分布的结构: 栈区(stack):由系统自动分配,一般存放函数参数值.局部变量的值等.由编译器自动创建与释放.其操作方式类似于数据结构中的栈,即后进先出.先进后出的原则. 例如:在函数中申明一个局部变量int b;系统自动在栈中为b开辟空间. 堆区(heap):一般由程序员申请并指明大小,最终也由

Objective-C中的Block[转]

1.相关概念 在这篇笔记开始之前,我们需要对以下概念有所了解. 1.1 操作系统中的栈和堆 注:这里所说的堆和栈与数据结构中的堆和栈不是一回事. 我们先来看看一个由C/C++/OBJC编译的程序占用内存分布的结构: 栈区(stack):由系统自动分配,一般存放函数参数值.局部变量的值等.由编译器自动创建与释放.其操作方式类似于数据结构中的栈,即后进先出.先进后出的原则. 例如:在函数中申明一个局部变量int b;系统自动在栈中为b开辟空间. 堆区(heap):一般由程序员申请并指明大小,最终也由

Block用法解析

Block代码块是对C做出的一个补充,初次体验代码块,无比的不适应.古怪的语法确实让人不易入门甚至精通.但若能够深入精通Block的使用,会给代码带来层次上的提升.Block就是一段在将来被执行的代码 定义块语法 ^[块返回值类型](形参类型1 形参1,形参类型2 形参2,...) { //块执行任务 } 需要注意的是定义块必须以^开头 定义块变量 块返回值类型 (^块变量名)(形参类型1,形参类型2,...); 简单实例 //定义块变量 void(^NSLogStr)(); //通过块变量调用

java中runnable的用法解析

1.类结构,首先有两个类,一个是测试类Main,一个是线程类MyThread. 2.MyThread package com.test.thread; import java.lang.management.ThreadInfo; public class MyThread implements Runnable { String name=null; MyThread(String name) { this.name=name; } @Override public void run() {