Block使用的簡單總結


【Block的簡單使用】
  1. block 當作參數來傳遞
  如下定義一個沒有返回值無參數的 block ,並把它作為參數,讓系統調用,注意:這里是系統在調用,因為 UIView 動畫結束是系統調用的。
    void(^myBlock)(void) = ^() {
        NSLog(@"定義了一個 Block");
    };
    [UIView animateWithDuration:0.3 animations:myBlock];

  那么為什么需要把 block 當作參數去使用呢?

  這就引出了 block 這個時候的使用場景:當自己封裝一個類的時候,有些事情由外部決定,但什么時候做由內部決定,(即內部決定執行時間,外部傳入具體做些什么)——這個時候就可以使用 block 來作為參數。

   2. block 當作返回值來使用
  如下在控制器寫下面代碼, test 為方法名, void(^)(void) 就是 block 的類型,它就是方法的返回值類型。
- (void(^)(void))test {
    return ^{
        NSLog(@"test");
    };
}

  那么上面這個方法該如何調用呢?

  第一種常規方式: [self test](); ,因為 test 是一個方法,所以 [self test] 調用方法后得到的是一個 block,而執行 block 只要在它后面加  () 就可以。所以 [self test](); 打印出了 test。

  第二種方式: self.test(); 這樣類似於屬性的 get 方法,后面加一個  () 就相當於執行 block。這里可以留存一個疑問:oc 的 get 方法其實本質是 Xcode 的編譯器特性,使用點語法可以對應到屬性的 get 方法。那為什么方法返回值為 block 時使用點語法就能被調用到了呢?(待深究)

   block 鏈式編程
  把方法調用通過語法鏈接起來,可讀性非常好。如下代碼,AddTool 繼承自 NSObject,用它來執行鏈式累加。
  .h 文件
#import <Foundation/Foundation.h>

@interface AddTool : NSObject

- (AddTool *(^)(NSInteger))add;

@property (nonatomic, assign) NSInteger result;

@end

  .m 文件

#import "AddTool.h"

@implementation AddTool

- (AddTool *(^)(NSInteger))add
{
    return ^(NSInteger value) {
        _result += value;
        return self;
    };
}

@end

  鏈式調用如下:

    AddTool *addTool = [AddTool new];
    NSInteger result = addTool.add(5).add(3).add(1).result; // NSLog: 9

 

【Block 逆傳值】

  這里用控制器跳轉舉例 block 的逆傳值。A push 到 B 之后,B 傳值到 A 中。可以在 B 頭文件聲明一個 block 屬性,在 .m 文件中執行這個 block。

  B  .h 文件:

@interface BViewController : UIViewController

@property (nonatomic, strong) void(^callBack)(NSString *str);

@end

  B .m 文件

- (void)viewDidLoad {
    [super viewDidLoad];
    self.callBack(@"123");
}

  在 A 中 push 處的代碼就可以收到 B 傳過來的值。

    BViewController *bVC = [[BViewController alloc] init];
    bVC.callBack = ^(NSString *str) {
        NSLog(@"%@", str); // NSLog: 123
    };
    [self.navigationController pushViewController:bVC animated:YES];

 

【Block的循環引用】

  循環引用的描述:Block的擁有者在Block作用域內部又引用了自己,因此導致了Block的擁有者永遠無法釋放內存,就出現了循環引用的內存泄漏。

  發生的場合:ARC 中 block 為 strong 或 copy 屬性,在Block內部使用了當前類的self屬性,同時這個類包含了別一個類的 Block 屬性。

舉例:還是上面的傳值的例子,現假如在A的.m文件的block里使用了self.view.backgroundColor = [UIColor redColor] 現在這些對象之間的引用關系如下圖所示:
因此這樣就就造成了循環引用。
 
Block 內循環引用的解決:
在如下的例子,可以在 Block 外加上這樣一句(如果將 typeof 用於表達式,則該表達式不會執行。只會得到該表達式的類型。)
  __weak typeof(self) weakSelf = self; 或   __weak AViewController *weakSelf = self; 
 
【Block 內存分析】
  1. 內存5大區 —— 堆, 棧, 方法區,靜態區(全局區),常量區
  • 全局區和靜態區: 它們其實是一樣的,從內存上來看,全局變量和靜態變量都是保存在靜態存儲區,生命期和程序一樣,都是在靜態數據區分配內存。在程序結束后回收內存。
   它們之間作用域有所不同,全局變量的作用域是整個項目,靜態全局變量是當前程序文件,靜態局部變量是當前函數體內。
  • : 手動管理內存    
  • 棧: 代碼塊一過,系統自動回收對應內存區
 首先來了解一下 Block,蘋果官方文檔 Block 是對象。(因為它是對象,所以用%@格式打印可以看到它的內存分配區),文檔描述第一句話如下:
 

  2. MRC 下 Blcok 內存分配

    在 ARC 之前,Block 的內存管理需要區分是 Global (全局)、Stack (棧) 還是 Heap (堆)。

  • 全局區: 如下定義一個Block,並將它使用, 它的打印結果為 <__NSGobalBlock__: 0x1075c076> ,global 表示全局區。全局區表示到處都可以使用。(ps:假如block內部訪問static修辭的外部局變量,那么它也是在全局區,關於靜態區與全局剛剛上面已經解釋過)  
    void(^block)() = ^{
        
    };
    NSLog(@"%@", block);
  • 棧區:如下當Block內訪問一個外面的局部變量a,它的打印結果為 <__NSStockBlock__: 0x7fff5baa09d8> ,stack表示棧區
    int a = 0;
    // block 存放在全局區
    void(^block)() = ^{
        NSLog(@"%d", a);
    };
總結如下:Block的內存分配與它內部訪問的變量有關,如果訪問的是全局變量,那Block會在全局區;  如果訪問局部變量,那 Block 會分配到棧區。(什么也不訪問默認在全局區)
  • 堆區:使用 copy 進行強引用時 block 會 copy 一份到堆區

  在 MRC 環境下用 retain 修辭 Block 會有黃色警告,如下圖,警告提示最好使用 copy

 
假如現在就用 retain,並且在 block 內訪問了一個局部變量 a (這個 a 就寫在 Block 的上面),代碼如下,這時發現一運行程序就漰了。(壞內存訪問,報錯會出現在使用 self.Block 的地方)
    int a = 0;
    void(^block)() = ^{
        NSLog(@"調用block");
        NSLog(@"%d", a);
    };
    
    self.block = block; // 報錯
這一點總結:在非ARC下不能用 retain 引用 block,因為這樣不會把 block 放在堆里,它會在棧區,所以代碼區一出括號就會銷毀,於是會報錯。只要使用 copy 就可以把 block 放到堆里面。這樣block就不會銷毀。——所以說 block 要用 copy 都是老程序員說的,因為那時沒有ARC.
 
  3. ARC 下 Block 內存分配
  ARC之后,蘋果自動會將所有原本應該放在棧中的Block全部放到堆中。

  首先,全局的 Block 比較簡單,一句話就可以講完:凡是沒有引用到 Block 作用域外面的參數的 Block 都會放到全局內存塊中,在全局內存塊的 Block 不用考慮內存管理問題。(放在全局內存塊是為了在之后再次調用該 Block 時能快速反應,當然沒有調用外部參數的 Block 根本不會出現內存管理問題)。

   所以 Block 的內存管理出現問題的,絕大部分都是在堆內存中的 Block 出現了問題。實際上屬於 Block 特有的內存管理問題就只有一個:循環引用。

  • 全局區:還是如下代碼,默認 Block 還是放在全局區,沒訪問外部變量就都是在全局區,與是否ARC無關
    void(^block)() = ^{
        
    };
    NSLog(@"%@", block);
  • 堆區: 訪問一個外部局部變量或對象時,代碼如下,它的打印結果 <__NSMallocBlock__: 0x7f9f5baa09d8> ,這個 malloc 分配的內存是表示在堆上。(這有點像ARC下默認一個對象就是強引用,好處是不會一創建就銷毀。)
    int a = 0;
    void(^block)() = ^{
        NSLog(@"%d", a);
    };
    NSLog(@"%@", block);

 

 【Block 內變量值傳遞與指針傳遞】
  看下面代碼,這個很簡單,block 打印出來肯定是 10
    int a = 5;
    a = 10;
    void(^block)() = ^{
        NSLog(@"%d", a); // NSLog: 10
    };
    block();

  1. 值傳遞情況

  再看如下代碼,這個打印出來是5,雖然 block() 執行之前 a = 10,但因為 a = 5 之后就立即被傳遞進了 block,只是還沒有執行而已,即內存已配好。而且這時的 block 是值傳遞,這時外面更改無法改變里面的值。(如果是指針傳遞則外面可以更改里面的值)

    int a = 5;
    // 值傳遞
    void(^block)() = ^{
        NSLog(@"%d", a); // NSLog: 5
    };
    a = 10;
    block();
  
  2. 指針傳遞情況 
  那么再看如下代碼,這個打印結果會是10。原因:只要局部變量生命周期是整個 app 運行都在,那么就是指針傳遞這里加 static 后,生命周期就與整個程序同存亡了。與全局區一樣(這一點上面 Block 內存分析時分析過了)
    static int a = 5;
    // 指針傳遞
    void(^block)() = ^{
        NSLog(@"%d", a); // NSLog: 10
    };
    a = 10;
    block();

  下在情況也是指針傳遞,所以打印出來也會是10

    __block int a = 5;
    // 指針傳遞
    void(^block)() = ^{
        NSLog(@"%d", a); // NSLog: 10
    };
    a = 10;
    block();

  block變量傳遞總結:如果在block內部訪問的是局部變量,那么就是值傳遞,否則就是指針傳遞。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM