1. 程式人生 > >iOS block從零開始

iOS block從零開始

ogr 追溯 解析 循環 橫空出世 返回值 weak void 新的

iOS block從零開始


在iOS4.0之後,block橫空出世,它本身封裝了一段代碼並將這段代碼當做變量,通過block()的方式進行回調。

block的結構

先來一段簡單的代碼看看:


void (^myBlock)(int a) = ^(int a){
        NSLog(@"%zd",a);
    };

    NSLog(@"旭寶愛吃魚");

    myBlock(999);

輸出結果:

2016-05-03 11:27:18.571 block[5340:706252] 旭寶愛吃魚
2016-05-03 11:27:18.571 block[5340:706252] 999

下面我們解析一下:

  • void :返回的參數 void為沒有返回值
  • (^myBlock):myBlock 為 block 的名稱
  • (int a):此為參數列表
  • ^(int a):傳入參數

通過上面的簡單介紹可以簡單了解到block的結構那麽下面便產生了四種格式的block。

四種block

有返回值無參:

int (^myBlock)() = ^(){

        return 999;

    };

有返回值有參:

int (^myBlock)(int a) = ^(int a){

        NSLog(@"%zd",a);
        return a;

    };

無返回值無參:

void (^myBlock)() = ^(){

        NSLog(@"旭寶愛吃魚");

    };

無返回值有參:

void (^myBlock)(int a) = ^(int a){

        NSLog(@"%zd",a);

    };

block 捕獲外界局部變量

示例代碼:

int a = 10;

    void (^myBlock)() = ^(){

        NSLog(@"旭寶愛吃魚");
        NSLog(@"%zd",a);

    };

    NSLog(@"旭寶愛吃魚");

    myBlock();

運行結果:

2016-05-03 11:43:32.680 block[5406:713702] 旭寶愛吃魚
2016-05-03 11:43:32.681 block[5406:713702] 旭寶愛吃魚
2016-05-03 11:43:32.681 block[5406:713702] 10

通過示例代碼不難發現我們可以獲取局部變量,那麽改變局部變量是否也會改變block內的值呢。

示例代碼:

int a = 10;

    void (^myBlock)() = ^(){

        NSLog(@"旭寶愛吃魚");
        NSLog(@"%zd",a);

    };

    a = 20;

    NSLog(@"旭寶愛吃魚");

    myBlock();

運行結果:

2016-05-03 11:47:07.669 block[5425:715861] 旭寶愛吃魚
2016-05-03 11:47:07.670 block[5425:715861] 旭寶愛吃魚
2016-05-03 11:47:07.670 block[5425:715861] 10

正如結果顯示,block內部的值是不會改變的,為什麽呢????

示例代碼:

int a = 10;

    a = 20;

    void (^myBlock)() = ^(){

        NSLog(@"旭寶愛吃魚");
        NSLog(@"%zd",a);

    };

    NSLog(@"旭寶愛吃魚");

    myBlock();

運行結果:

2016-05-03 11:49:19.749 block[5450:717309] 旭寶愛吃魚
2016-05-03 11:49:19.750 block[5450:717309] 旭寶愛吃魚
2016-05-03 11:49:19.750 block[5450:717309] 20

情理之中的答案。
之所以block內部的值不會改變是因為block copy了局部變量。

這個問題解決後嘗試在block中改變局部變量。
很不幸失敗了,當我在block內改變外界的局部變量時,報錯了。
可視化我想改變外界的局部變量我該怎麽辦呢???

改變外界的局部變量

示例代碼:

__block int a = 10;


    void (^myBlock)() = ^(){

        NSLog(@"旭寶愛吃魚");
        NSLog(@"%zd",a);

        a = 20;

    };

    NSLog(@"旭寶愛吃魚");

    myBlock();

    NSLog(@"%zd",a);

運行結果:

2016-05-03 11:55:02.736 block[5490:721033] 旭寶愛吃魚
2016-05-03 11:55:02.737 block[5490:721033] 旭寶愛吃魚
2016-05-03 11:55:02.737 block[5490:721033] 10
2016-05-03 11:55:02.737 block[5490:721033] 20

我們只需要在局部變量前加__block 即可。

循環引用(我在前面的博客有所提及)

block在iOS開發中被視作是對象,因此其生命周期會一直等到持有者的生命周期結束了才會結束。另一方面,由於block捕獲變量的機制,使得持有block的對象也可能被block持有,從而形成循環引用,導致兩者都不能被釋放:

@implementation CXObject
{
   void (^_cycleReferenceBlock)(void);
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    _cycleReferenceBlock = ^{ 
        NSLog(@"%@", self);   //引發循環引用
    };
}

@end

遇到這種代碼編譯器只會告訴你存在警告,很多時候我們都是忽略警告的,這最後會導致內存泄露,兩者都無法釋放。跟普通變量存在block關鍵字一樣的,系統提供給我們weak的關鍵字用來修飾對象變量,聲明這是一個弱引用的對象,從而解決了循環引用的問題:

__weak typeof(*&self) weakSelf = self;
_cycleReferenceBlock = ^{ 
    NSLog(@"%@", weakSelf);   //弱指針引用,不會造成循環引用
};

使用block

在block出現之前,開發者實現回調基本都是通過代理的方式進行的。比如負責網絡請求的原生類NSURLConnection類,通過多個協議方法實現請求中的事件處理。而在最新的環境下,使用的NSURLSession已經采用block的方式處理任務請求了。各種第三方網絡請求框架也都在使用block進行回調處理。這種轉變很大一部分原因在於block使用簡單,邏輯清晰,靈活等原因。接下來我會完成一次網絡請求,然後通過block進行回調處理。這些回調包括請求完成、下載進度

按照returnValue(^blockName)(parameters)的方式進行block的聲明未免麻煩了些,我們可以通過關鍵字typedef來為block起類型名稱,然後直接通過類型名進行block的創建:

@interface CXDownloadManager: NSObject

//block重命名
typedef void(^CXDownloadHandler)(NSData * receiveData, NSError * error);
typedef void(^CXDownloadProgressHandler)(CGFloat progress);

- (void)downloadWithURL: (NSString *)URL parameters: (NSDictionary *)parameters handler: (CXDownloadHandler)handler progress: (CXDownloadProgressHandler)progress;

@end

@implementation CXDownloadManager
{
    CXDownloadProgressHandler _progress;
}

- (void)downloadWithURL: (NSString *)URL parameters: (NSDictionary *)parameters handler: (CXDownloadHandler)handler progress: (CXDownloadProgressHandler)progress
{
    //創建請求對象
    NSURLRequest * request = [self postRequestWithURL: URL params: parameters]; 
    NSURLSession * session = [NSURLSession sharedSession];

    //執行請求任務
    NSURLSessionDataTask * task = [session dataTaskWithRequest: request completionHandler: ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (handler) {
            dispatch_async(dispatch_get_main_queue(), ^{
                handler(data, error);
            }); 
        }
    }];
    [task resume];
}

//進度協議方法
- (void)URLSession:(NSURLSession *)session
     downloadTask:(NSURLSessionDownloadTask *)downloadTask 
    didWriteData:(int64_t)bytesWritten // 每次寫入的data字節數  
   totalBytesWritten:(int64_t)totalBytesWritten // 當前一共寫入的data字節數  
  totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite // 期望收到的所有data字節數  
{   
    double downloadProgress = totalBytesWritten / (double)totalBytesExpectedToWrite;  
    if (_progress) { _progress(downloadProgress); }
}  

@end

上面通過封裝NSURLSession的請求,傳入一個處理請求結果的block對象,就會自動將請求任務放到工作線程中執行實現,我們在網絡請求邏輯的代碼中調用如下:


#define QQMUSICURL @"https://www.baidu.com/link?url=UTiLwaXdh_-UZG31tkXPU62Jtsg2mSbZgSPSR3ME3YwOBSe97Hw6U6DNceQ2Ln1vXnb2krx0ezIuziBIuL4fWNi3dZ02t2NdN6946XwN0-a&wd=&eqid=ce6864b50004af120000000656fe235f"
[[CXDownloadManager alloc] downloadWithURL: QQMUSICURL parameters: nil handler ^(NSData * receiveData, NSError * error) {
    if (error) { NSLog(@"下載失敗:%@", error) }
    else {
        //處理下載數據
    }
} progress: ^(CGFloat progress) {
    NSLog(@"下載進度%lu%%", progress*100);
}];

總結

block捕獲變量、代碼傳遞、代碼內聯等特性賦予了它多於代理機制的功能和靈活性,盡管它也存在循環引用、不易調試追溯等缺陷,但無可置疑它的優點深受碼農們的喜愛。如何更加靈活的使用block需要我們對它不斷的使用、探究了解才能完成

iOS block從零開始