zoukankan      html  css  js  c++  java
  • 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 实体会长成这样

     

    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。

  • 相关阅读:
    HUST 1372 marshmallow
    HUST 1371 Emergency relief
    CodeForces 629D Babaei and Birthday Cake
    CodeForces 629C Famil Door and Brackets
    ZOJ 3872 Beauty of Array
    ZOJ 3870 Team Formation
    HDU 5631 Rikka with Graph
    HDU 5630 Rikka with Chess
    CodeForces 626D Jerry's Protest
    【POJ 1964】 City Game
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/4997615.html
Copyright © 2011-2022 走看看