iOS 編寫高質量Objective-C代碼(六)

級別: ★★☆☆☆
標籤:「iOS」「Block」「Objective-C」
做者: MrLiuQ
審校: QiShare團隊
php

前言: 這幾篇文章是小編在鑽研《Effective Objective-C 2.0》的知識產出,其中包含做者和小編的觀點,以及小編整理的一些demo。但願能幫助你們以簡潔的文字快速領悟原做者的精華。 在這裏,QiShare團隊向原做者Matt Galloway表達誠摯的敬意。html

文章目錄以下:
iOS 編寫高質量Objective-C代碼(一)
iOS 編寫高質量Objective-C代碼(二)
iOS 編寫高質量Objective-C代碼(三)
iOS 編寫高質量Objective-C代碼(四)
iOS 編寫高質量Objective-C代碼(五)
iOS 編寫高質量Objective-C代碼(六)
iOS 編寫高質量Objective-C代碼(七)
iOS 編寫高質量Objective-C代碼(八)git


本篇的主題是iOS中的 「Block的原理及應用」github

先簡單介紹一下今天的主角:blockbash

  • block(塊):是一種 「 詞法閉包 」,經過block,開發者可將代碼塊像對象同樣傳遞。

1、理解「block」的概念:

1. block的數據結構:

經過clang命令行工具(OC轉C++),咱們先來看一下block的內部數據結構大概是什麼樣子的?微信

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};
複製代碼

解析:很顯然,Block_layout是一個結構體:裏面有一個isa指針,指向Class對象。還有一個函數指針,指向了塊的實現代碼。數據結構

block的數據結構


2. block的三種類型:全局塊、棧塊、堆塊。

根據block在內存中的位置,block被分紅三種類型:閉包

類型 內存位置 介紹
__NSStackBlock__ 棧區 棧內有效,出棧後銷燬。
__NSMallocBlock__ 堆區 copy到堆空間上。能夠在定義的那個範圍以外使用。
__NSGlobalBlock__ 全局區 不捕捉任何外部變量,所有信息在編譯器就已肯定。

  • 1. NSStackBlock 棧塊: 棧塊保存於棧區,超出變量做用域,棧上的block以及聲明的_block都會被銷燬。

例如:異步

__block NSString *name = @"QiShare";
void (^block)(void) = ^{
    NSLog(@"%@ is an iOS team which loves to share technology.", name);
};
NSLog(@"block = %@", block);
複製代碼

小知識點:當block內部須要修改或訪問外部變量時,外部變量須要額外用__block修飾。不然修改不了。async

咱們來看下打印:

ARC的場景

什麼?竟然是__NSMallocBlock__(堆塊)? 那是由於ARC環境下,編譯器自動幫咱們加了copy操做。

這時咱們關掉ARC:設置Objective-C Automatic Reference Counting = NO。再來看下打印:

MRC場景


  • 2. NSMallocBlock 堆塊: 堆block內存位於堆區,在變量做用域結束時依然可使用。

經過上面的例子: 在ARC下,block會默認加上copy操做:變成__NSMallocBlock__


  • 3. NSGlobalBlock 全局塊: 塊中無任何外界對象,所需的內存在編譯時就能夠肯定,內存位於全局區。 相似於「單例」,copy是一個空操做。

例如:

void (^qiShare)(void) = ^{
    
    NSLog(@"We love sharing.");
};
NSLog(@"%@",qiShare);
複製代碼

2、爲經常使用的block類型建立typedef

爲了增長代碼的 可讀性可拓展性, 須要爲經常使用的block起個別名。

typedef爲塊起別名,也可令塊變量用起來更加簡單~ 好比:

- (void)getDataWithToken:(NSString *)token success:(void (^)(id responseDic))success;

//! 以上要改爲下面這種
typedef void (^SuccessBlock)(id responseDic);
- (void)getDataWithToken:(NSString *)token success:(SuccessBlock)success;
複製代碼

3、用handler塊下降代碼分散程度

在咱們iOS開發中,常常會異步執行一些任務,等待任務執行結束後再通知對象調用相關方法。 通常有三種作法:

  • 第一種:使用NSNotificationCenter:NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  • 第二種:使用委託協議:詳情見iOS 編寫高質量Objective-C代碼(四)
  • 第三種:使用block回調:直接把block對象當作參數傳給相關方法執行。

舉個例子:AFNetworking的API設計及使用就是block回調

  • 接口設計:
- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
                       success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                       failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {

    return [self POST:URLString parameters:parameters progress:nil success:success failure:failure];
}
複製代碼
  • 使用:
AFHTTPSessionManager *manger =[AFHTTPSessionManager manager];
    NSString *urlString = @"";
    NSMutableDictionary *parameter= @{@"":@"",@"":@""};
    
    [manger POST:urlString
            parameters:parameter 
            success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                NSLog(@"成功");
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                NSLog(@"%@",error);
            }];
}
複製代碼

4、用block引用其所屬對象時避免出現循環引用

在咱們平常開發中,若是block使用不當,很容易致使內存泄漏。

  • 理由:若是block被當前ViewController(self)持有,這時,若是block內部再持有ViewController(self),就會形成循環引用。
  • 解決方案:在block外部對弱化self,再在block內部強化已經弱化的weakSelf

For Example:

__weak typeof(self) weakSelf = self;

[self.operationQueue addOperationWithBlock:^{

    __strong typeof(weakSelf) strongSelf = weakSelf;

    if (completionHandler) {

        KTVHCLogDataStorage(@"serial reader async end, %@", request.URLString);      
        completionHandler([strongSelf serialReaderWithRequest:request]);
    }
}];
複製代碼

固然,也不是全部block中使用到self都要先弱化成weakSelf,再強化成strongSelf, 只要block沒有被self所持有的,在block中就可使用self。 好比下面:

[QiNetwork requestBlock:^(id responsObject) {
      NSLog(@"%@",self.name);
  }];
複製代碼

小貼士:內存泄漏檢測相關知識請看:iOS 內存泄漏排查方法及緣由分析


關注咱們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公衆號)


推薦文章:
奇舞週刊278期
iOS 推送通知

相關文章
相關標籤/搜索