zoukankan      html  css  js  c++  java
  • objective-c 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 實體會長成這樣

    ^(傳入參數列) {行為主體};


    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); 

    // 有一個叫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(  ^(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 copyautorelease] ;
    }

    -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。

    转:http://www.j2megame.org/index.php/content/view/2625/165.html

  • 相关阅读:
    HTML DOM教程 14HTML DOM Document 对象
    HTML DOM教程 19HTML DOM Button 对象
    HTML DOM教程 22HTML DOM Form 对象
    HTML DOM教程 16HTML DOM Area 对象
    ubuntu 11.04 问题 小结
    VC6.0的 错误解决办法 小结
    boot.img的解包与打包
    shell里 截取字符串
    从零 使用vc
    Imagemagick 对图片 大小 和 格式的 调整
  • 原文地址:https://www.cnblogs.com/ygm900/p/3176320.html
Copyright © 2011-2022 走看看