1. 程式人生 > >IOS開發 block(程式碼塊)基本使用

IOS開發 block(程式碼塊)基本使用

1. block基本概念:

(開篇廢話)
Block是C級別的語法和執行時特性。Block比較類似C函式,但是Block比之C函式,其靈活性體現在棧記憶體、堆記憶體的引用。

Block是蘋果推薦的型別,效率高,可以幫助我們組織獨立的程式碼段,並提高複用性和可讀性。主要是用來在執行中封裝程式碼和儲存程式碼用的。

Block可以在任何時候被執行。

和c語言的比較:

1、可以儲存程式碼。
2、有返回值。
3、有引數
4、呼叫方式一樣

(函式和函式是同級的關係,函式裡面不能定義行數,但是block可以定義在程式的任何地方,只要遵循一條原則:程式碼是從上到下執行的,先定義後使用)

最簡單地理解:block就是一個用來儲存程式碼的變數,可以在你需要的使用的時候通過block 來使用你儲存的程式碼,通常用來做併發任務、遍歷、以及回撥。

格式說明:

(返回型別)(^塊名稱)(引數型別列表) = ^(形參列表) {程式碼實現};
如果沒有引數,等號後面引數列表的()可以省略

2. block 在開發或者系統架構中的使用

從 xcode 4.0 開始,系統類庫中的函式越來越多的開始使用 block 作為引數,以下是在系統函式中使用程式碼塊的部分情況
    a. 遍歷陣列和字典
    b. 排序
    c. 檢視動畫
    d. 結束回撥
    e. 錯誤處理
    f. 多執行緒等

3. 必須瞭解的東西

廢話說完後來點重點:
a. block(程式碼塊)
   是 oc中的一種資料型別,可以被當做引數傳遞,可以有返回值,是一個能工作的程式碼單元,可以在任何需要的時候被執行,就像呼叫函式一樣呼叫。
   在 ios 開發中廣泛使用。

b. ^ 是 block 的特有的標記。
c. block   熟練了解block 的定義(塊程式碼的定義),記得實現程式碼包含在 {} 之間。
d. block 是以內聯 inline 函式的方式被定義使用。
e. 本質上是輕量級的匿名函式。

c. 塊程式碼的使用注意點
    i. 預設情況下,不允許在塊程式碼內部修改外部的變數的數值
    ii. __block,讓外部的變數能夠在block中修改。
    iii. 迴圈引用的問題  __weak (ios5.0以下的版本使用__unsafe_unretained(廢話))
    iv. 預設情況下,block 外部的變數,在 block 中是隻讀的。
    v. 塊程式碼與代理的區別

4、block的定義

1、block定義和指向函式的指標的對比

定義除了一個符號發生了改變基本是一樣的:
這裡寫圖片描述

指向函式的指標和block定義的程式碼對比:

#import <Foundation/Foundation.h>

// 測試函式
void test(){
    NSLog(@"%s",__func__);
}
/*
 對於指標不理解的:函式名就是指向函式的指標
 */
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // 這個是定義了一個函式的指標並賦值
        void
(*Mytest)() = test; // 呼叫函式 Mytest(); // 定義一個沒有返回值的block void(^Myblock)() = ^{ NSLog(@"這個是Myblock"); }; // 呼叫block Myblock(); /* void (*Mytest)(); void(^Myblock)(); 1、指向函式的指標和block的定義的對比,基本只有一個符號的區別。 2、使用函式的指標和block的使用基本是一樣的。需要呼叫才會執行。 指向函式的指標是通過函式名的指向的地址呼叫函式。 block是直接執行一段儲存的程式碼。 */ } return 0; } 如果要糾結啥子有返回值沒有返回值有引數沒有引數的block和指向函式的指標的對比那就自己去玩去。 列印結果: 2015-04-22 23:03:18.965 block[4274:1360943] test 2015-04-22 23:03:18.966 block[4274:1360943] 這個是Myblock

2、block定義—— 沒有引數沒有返回值的block的定義

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 定義block ————> 沒有引數沒有返回值的
        /*
         定義的時候,將block當成資料型別

         特點:
         1、型別比函式多了一個 ^
         2、設定數值,有一個^ ,內容是{}括起來的一段程式碼

         最簡單的方式:不帶返回值不帶引數。
         void (^Myblock)() = ^ {
            // 要儲存的程式碼實現;
         };
         */

        // 定義一個block 並儲存一段程式碼
        void (^Myblock)() = ^{
            NSLog(@"Myblock");   // 這個是儲存的程式碼
        };  // 這個; 號是不能少的

        // 呼叫block
        Myblock(); // 像呼叫函式一樣呼叫block

    }
    return 0;
}

列印結果:
2015-04-22 23:22:04.593 block[4306:1412796] Myblock

3、block定義—— 有引數沒有返回值的block的定義

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 定義block ————> 有引數沒有返回值的
        void (^Myblock)(int) = ^(int num){  // 當有引數的時候 ^(int num){ 中間的()是不能少的
            NSLog(@"Myblock,傳入的引數是:%d",num );
        };

        // 呼叫block
        Myblock(10);
    }
    return 0;
}

列印的結果:
2015-04-22 23:28:48.488 block[4317:1439967] Myblock,傳入的引數是:10

4、block定義—— 有引數有返回值的block的定義

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 定義block ————> 有引數有返回值的
        int (^Myblock)(int) = ^(int num){
            NSLog(@"Myblock,傳入的引數是:%d",num );
            return num; //當定義的block 是有返回值的時候一定要返回要不就會報錯
        };

        // 呼叫block
        Myblock(10);  // block有返回值並不一定要接收
        NSLog(@"%d", Myblock(20));
    }
    return 0;
}

列印的結果:
2015-04-22 23:34:34.298 block[4354:1462221] Myblock,傳入的引數是:10
2015-04-22 23:34:34.301 block[4354:1462221] Myblock,傳入的引數是:20
2015-04-22 23:34:34.301 block[4354:1462221] 20

5、block定義—— 有引數有返回值的block的定義可能看見的還有一種寫法

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 定義block ————> 有引數有返回值的
        int (^Myblock)(int,int) = ^int(int num1 ,int num2){
            NSLog(@"Myblock,傳入的引數num1是:%d num2是%d",num1,num2);
            return num1 + num2; //當定義的block 是有返回值的時候一定要返回要不就會報錯
        };

        // 呼叫block
        Myblock(10,20);  // block有返回值並不一定要接收
        NSLog(@"%d", Myblock(20,30));
    }
    return 0;
}

 ^int( 這個中間的int時返回值型別可以省略不寫

列印的結果:
2015-04-22 23:43:48.958 block[4378:1501255] Myblock,傳入的引數num1是:10 num2是20
2015-04-22 23:43:48.959 block[4378:1501255] Myblock,傳入的引數num1是:20 num2是30
2015-04-22 23:43:48.959 block[4378:1501255] 50

其他的什麼資料型別引數和返回值的排列組合就不一一列之舉,想玩的自己去試試。

6、block的祕籍——block的書寫是我們最蛋疼的事,一招解決所有

使用inlineblock這個速記符號可以快速的敲出block的基本結構
這裡寫圖片描述

注意:block的書寫一定要熟記。
inlineblock這個速記符號只能用來輔助記憶。
(如果你不知道填空大話,我也是醉了)

5、關於block幾個疑惑和容易出錯的問題

1、block在記憶體中的位置

棧裡面的東西是不需要我們程式設計師管理的,記憶體中唯一一個需要程式設計師管理的是堆,我們在寫程式碼的時候new出來的東西都在堆中。
block預設情況下是在棧中的,是不需要我們程式設計師管理的。如果多block進行了一次copy操作,就會將block轉移到堆中。

注意點:
如果block 在棧裡,那麼block中用到了外界的物件,不用我麼管理。
但是如果block在堆中,那麼block中如果用到外界物件,會對物件進行一次retain操作,也就是會進行強引用。
如果想讓堆中的block不對使用到的外界物件進行retain,那麼就只需要在外界物件的前面加__block.

2、關於block引用外部變數操作的問題

block 使用,如果引用了外部變數,就會對外部變數做一個copy的操作。記錄住定義block時候的值。如果後續再修改外部變數的值,不會影響block內部的數值的變化!

外部變數本來是在棧區中,block引用的那一刻,就將外部變數copy 到堆中了,block裡面使用的時copy 後堆中的變數的值。
所有在block 呼叫之前修改外部變數的值,不會影響block裡面值的原因。

如果要驗證:可以通過列印地址值的方式來驗證,棧區是高位地址值,相對於棧,堆在低位地址。
通過列印地址發現,block裡面的變數的地址值比block外面的地址值要小很多。
示例程式:
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int x = 10;
        void (^Myblock)() = ^{
            NSLog(@"%d",x); // 引用外部變數 (已經copy,記錄了外部變數的值)
        };
        x = 20;   // 修改外部變數的值
        Myblock();   
    }
    return 0;
}

列印結果:
2015-04-23 00:37:15.045 block[4470:1683462] 10

3、關於在block內部修改外部變數的值的問題

在預設的情況下,是不允許在block內部修改外部變數的值。
原因是:會破壞程式碼的可讀性,不易於維護。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool { 
        int x = 10;
        void (^Myblock)() = ^{
            x = 80;  // 這樣是不能修改的,這樣寫直接報錯
        };  
       Myblock();
    }
    return 0;
}

如果我們一定要再block的內部修改外部變數的值,必須在外部變數的前面新增 _ _block ,這樣才會允許修改。
使用__block,說明不在關係外部變數數值的具體變化。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {  
       __block int x = 10; //  必須在前面加 __block 才可以在block 中修改外部變數的值
        void (^Myblock)() = ^{
            x = 80;
        }; 
        Myblock();
    }
    return 0;
}

為什麼使用__block 會達到這個效果(可以通過跟蹤地址值發現問題)?
在定義block時,如果引用了外部變數使用了__block的變數。block定義之後,外部變數同樣會被copy到堆中,不同的是棧中的那一份沒有了,只保留了堆中的那一份。在block 中修改的那一份和 保留的那一份是同一份。所以可以修改。

3、關於在block內部修改外部變數的值 —— 一個蛋疼的問題

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // 指標記錄的是地址
        NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];
        NSLog(@"定義前 %p %p", strM, &strM);

        void (^myBlock)() = ^ {
            // 修改strM指標指向的內容
            [strM setString:@"lisi"];
            NSLog(@"inblock %p %p", strM, &strM);

            // 這句程式碼是修改strM指標指向的地址
            //        strM = [NSMutableString stringWithString:@"wangwu"];
        };
        NSLog(@"定義後 %p %p", strM, &strM);

        myBlock();
        NSLog(@"%@", strM);

    }
    return 0;
}

列印的結果:
2015-04-23 01:30:30.900 block[4566:1798733] 定義前 0x100406b30 0x7fff5fbff7d8
2015-04-23 01:30:30.901 block[4566:1798733] 定義後 0x100406b30 0x7fff5fbff7d8
2015-04-23 01:30:30.902 block[4566:1798733] inblock 0x100406b30 0x1004074f0
2015-04-23 01:30:30.902 block[4566:1798733] lisi

block copy的知識指標沒有copy變數的地址。在block修改的是變數。所以結果會變。

4、關於在block在MRC 中的使用注意(重要的面試題(在ARC開發的時代基本沒有主要是為了測試功底))

(小白略過)

#import <Foundation/Foundation.h>

// 塊程式碼可以當作引數,也可以當作返回值
typedef void(^eBlock)();

/**
 問
 -以下程式碼在ARC中有問題嗎?=》沒有問題
 -在MRC中有問題嗎?存在記憶體隱患,i和b都是區域性變數,出了作用域就會被釋放

 解決問題:
 -返回前使用     Block_copy
 -使用後,使用   Block_release

 網上錯誤答案 return [b copy];

 *********
 Product - Analyze (靜態分析)

 從程式碼結構上分析是否存在缺陷!本身並不會執行程式!並不能夠檢測到真正的記憶體洩漏!

 但是:只要是靜態分析工具發現的問題,通常都是需要提升的程式碼!

 靜態分析工具,是MRC開發時的利器!提前發現記憶體隱患!

 另外,在ARC開發時,如果程式要上架之前,建議使用靜態分析工具檢測一下,通常可以發現一些不注意的警告,有助於提升程式碼質量!尤其在使用到C語言框架的程式碼!
 */
eBlock myBlock() {
    int i = 10;
    eBlock b = ^ {
        NSLog(@"hello %d", i);
    };

    // 利用Block_copy將block以及內部的變數拷貝到堆中
    return Block_copy(b);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        eBlock bb = myBlock();
        bb();

        // 釋放堆中block
        Block_release(bb);
    }
    return 0;
}

6、block迴圈引用問題的解決

迴圈引用的結果就是導致物件無法釋放。
我們測試的最好的辦法是在物件中重寫dellac方法,看這個方法是否被呼叫。沒有呼叫說明存在迴圈引用。

在我們的IOS開發當中,什麼時候會出現迴圈引用:
在我們使用block的時候,如果block中使用到了self ,這個時候就需要關心迴圈引用的問題。

解決方案:__weak typeof(self) weakSelf = self;
 // 示例程式碼SDWebImage 框架使用的使用用的程式碼:
   __weak typeof(self) weakSelf = self;
    SDWebImageManager *manage = [SDWebImageManager sharedManager];
    [manage downloadImageWithURL:url options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        if (finished) {
            // 這裡是用 weakSelf 代替self 避免迴圈引用
            [weakSelf setupImageFrame: image];
        }
    }];

注意點:並不是所有的block中使用self都會有迴圈引用的問題。為了避免迴圈引用的問題,遇到block中用到self 。我們都這麼寫,就可以避免迴圈引用的問題。

7、代理和block在使用的時候我們是怎麼選擇的。

委託和block是IOS上實現回撥的兩種機制。Block基本可以代替委託的功能,而且實現起來比較簡潔,比較推薦能用block的地方不要用委託。
單就程式設計過程而言,block對開發者處理邏輯,程式設計效率,程式碼閱讀都有積極影響。

代理是一種很金典的模式,我們很多人都已經習慣了這種模式,若果對block的回撥傳值的過程不是很理解的話,建議使用代理。可以達到同樣地效果。

一下是還未完善的區域,我會持續的更新的

8、 block 在 IOS 開發的實際運用

在實際的開發中block的使用基本就是傳值和回撥。這個也是難點和重點。

1、 block 在 IOS 開發的實際運用

3、asdfa

1、