iOS-Block 淺談

前言:Block 是開發(fā)過程中常用便捷的回調(diào)方式,本文簡單介紹 Block

一、Block 簡介

Block 對象是 C 級別的語法和運行時特性,和標準的 C 函數(shù)類似,除了可執(zhí)行代碼外,還可能包含變量自動綁定(棧)和內(nèi)存托管(堆)。一個 Block 維護一個狀態(tài)集。
閉包 = 一個函數(shù)「或指向函數(shù)的指針」+ 該函數(shù)執(zhí)行的外部的上下文變量「也就是自由變量」;Block 是 Objective-C 對于閉包的實現(xiàn)。

  • 可以嵌套定義,定義 Block 方法和定義函數(shù)方法相似
  • Block 可以定義在方法內(nèi)部或外部
  • 只有調(diào)用 Block 時候,才會執(zhí)行其{}體內(nèi)的代碼
  • 本質(zhì)是對象,使代碼高聚合

使用 clang 將 OC 代碼轉(zhuǎn)換為 C++ 文件查看 block 的方法:

  • 在命令行輸入代碼 clang -rewrite-objc 需要編譯的OC文件.m
  • 這時查看當前的文件夾里 多了一個相同的名稱的 .cpp 文件,在命令行輸入 open main.cpp 查看文件

1.1 定義和使用

1.無參數(shù)無返回值

void (^ MyBlockOne)(void) = ^(void){
    NSLog(@"無參數(shù),無返回值");  
};  
MyBlockOne();//block的調(diào)用

2.有參數(shù)無返回值

void(^MyblockTwo)(int a) = ^(int a){
    NSLog(@"@ = %d我就是block,有參數(shù),無返回值",a);
};  
MyblockTwo(100);

3.有參數(shù)有返回值

int(^MyBlockThree)(int,int) = ^(int a,int b){    
    NSLog(@"%d我就是block,有參數(shù),有返回值",a + b);
    return a + b; 
};  
MyBlockThree(12,56);

4.無參數(shù)有返回值

int(^MyblockFour)(void) = ^{
    NSLog(@"無參數(shù),有返回值");
    return45;
  };
MyblockFour();

5.定義聲明

聲明

typedef void (^Block)();
typedef int (^MyBlock)(int , int);
typedef void(^ConfirmBlock)(BOOL isOK);
typedef void(^AlertBlock)(NSInteger alertTag);

定義屬性

@property (nonatomic,copy) MyBlock myBlockOne;

使用

self.myBlockOne = ^int (int ,int){
    //TODO
}

1.2 Block與外界變量

1、截獲自動變量(局部變量)值

(1)默認情況
對于 block 外的變量引用,block 默認是將其復制到其數(shù)據(jù)結構中來實現(xiàn)訪問的。也就是說block的自動變量截獲只針對block內(nèi)部使用的自動變量, 不使用則不截獲, 因為截獲的自動變量會存儲于block的結構體內(nèi)部, 會導致block體積變大。特別要注意的是默認情況下block只能訪問不能修改局部變量的值。

int age = 10;
myBlock block = ^{
    NSLog(@"age = %d", age);
};
age = 18;
block();

輸出結果:
age = 10

(2) __block 修飾的外部變量

對于用 __block 修飾的外部變量引用,block 是復制其引用地址來實現(xiàn)訪問的。block可以修改__block 修飾的外部變量的值。

__block int age = 10;
myBlock block = ^{
    NSLog(@"age = %d", age);
};
age = 18;
block();

輸出為:
age = 18

2、__block 修飾的外部變量的值就可以被block修改

我們使用 clang 將 OC 代碼轉(zhuǎn)換為 C++ 文件:

clang -rewrite-objc 源代碼文件名
__block int val = 10;
轉(zhuǎn)換成
__Block_byref_val_0 val = {
    0,
    &val,
    0,
    sizeof(__Block_byref_val_0),
    10
};

會發(fā)現(xiàn)一個局部變量加上__block修飾符后竟然跟block一樣變成了一個__Block_byref_val_0結構體類型的自動變量實例。
此時我們在block內(nèi)部訪問val變量則需要通過一個叫__forwarding的成員變量來間接訪問val變量。

1.3 Block 存儲

1、Block的存儲域及copy操作

由C/C++/OBJC編譯的程序占用內(nèi)存分布的結構:


內(nèi)存分布結構

block有三種類型:

  • 全局塊(_NSConcreteGlobalBlock)
  • 棧塊(_NSConcreteStackBlock)
  • 堆塊(_NSConcreteMallocBlock)

三種block各自的存儲域:

  • 全局塊存在于全局內(nèi)存中, 相當于單例.
  • 棧塊存在于棧內(nèi)存中, 超出其作用域則馬上被銷毀
  • 堆塊存在于堆內(nèi)存中, 是一個帶引用計數(shù)的對象, 需要自行管理其內(nèi)存
block存儲域

簡而言之,存儲在棧中的Block就是棧塊、存儲在堆中的就是堆塊、既不在棧中也不在堆中的塊就是全局塊。

1.4 判斷Block的存儲位置

(1)Block不訪問外界變量(包括棧中和堆中的變量)
Block 既不在棧又不在堆中,在代碼段中,ARC和MRC下都是如此。此時為全局塊。
(2)Block訪問外界變量
MRC 環(huán)境下:訪問外界變量的 Block 默認存儲中。
ARC 環(huán)境下:訪問外界變量的 Block 默認存儲在中(實際是放在棧區(qū),然后ARC情況下自動又拷貝到堆區(qū)),自動釋放。

ARC下,訪問外界變量的 Block為什么要自動從棧區(qū)拷貝到堆區(qū)呢?
棧上的Block,如果其所屬的變量作用域結束,該Block就被廢棄,如同一般的自動變量。當然,Block中的__block變量也同時被廢棄。
為了解決棧塊在其變量作用域結束之后被廢棄(釋放)的問題,我們需要把Block復制到堆中,延長其生命周期。開啟ARC時,大多數(shù)情況下編譯器會恰當?shù)剡M行判斷是否有需要將Block從棧復制到堆,如果有,自動生成將Block從棧上復制到堆上的代碼。Block的復制操作執(zhí)行的是copy實例方法。Block只要調(diào)用了copy方法,棧塊就會變成堆塊。

棧塊copy堆塊

  • 在ARC的Block是配置在棧上的,所以返回函數(shù)調(diào)用方時,Block變量作用域就結束了,Block會被廢棄。種情況編譯器會自動完成復制。
  • 在非ARC情況下則需要開發(fā)者調(diào)用copy方法手動復制。
  • 將Block從棧上復制到堆上相當消耗CPU,所以當Block設置在棧上也能夠使用時,就不要復制了,因為此時的復制只是在浪費CPU資源。

Block的復制操作執(zhí)行的是copy實例方法。不同類型的Block使用copy方法的效果如下表:

Block的復制操作

根據(jù)表得知,Block在堆中copy會造成引用計數(shù)增加,這與其他Objective-C對象是一樣的。雖然Block在棧中也是以對象的身份存在,但是棧塊沒有引用計數(shù),因為不需要,我們都知道棧區(qū)的內(nèi)存由編譯器自動分配釋放。
不管Block存儲域在何處,用copy方法復制都不會引起任何問題。在不確定時調(diào)用copy方法即可。在ARC有效時,多次調(diào)用copy方法完全沒有問題:

blk = [[[[blk copy] copy] copy] copy];
// 經(jīng)過多次復制,變量blk仍然持有Block的強引用,該Block不會被廢棄。
1.5 __block變量與__forwarding

在copy操作之后,既然__block變量也被copy到堆上去了, 那么訪問該變量是訪問棧上的還是堆上的呢?__forwarding 終于要閃亮登場了。通過__forwarding, 無論是在block中還是 block外訪問__block變量, 也不管該變量在棧上或堆上, 都能順利地訪問同一個__block變量。

__block與__forwarding

1.6 Block 循環(huán)引用

Block 循環(huán)引用的情況:
某個類將 block 作為自己的屬性變量,然后該類在 block 的方法體里面又使用了該類本身。

self.someBlock = ^(Type var){
    [self dosomething];
};

解決辦法:
(1)ARC 下:使用 __weak

__weak typeof(self) weakSelf = self;
self.someBlock = ^(Type var){
   [weakSelf dosomething];
};

(2)MRC 下:使用 __block

__block typeof(self) blockSelf = self;
self.someBlock = ^(Type var){
   [blockSelf dosomething];
};

解決辦法:

//1.使用__weak ClassName
    __block XXViewController* weakSelf = self;
    self.blk = ^{
        NSLog(@"In Block : %@",weakSelf);
    };
//2.使用__weak typeof(self)
    __weak typeof(self) weakSelf = self;
    self.blk = ^{
        NSLog(@"In Block : %@",weakSelf);
    };
//3.Reactive Cocoa中的@weakify和@strongify
    @weakify(self);
    self.blk = ^{
        @strongify(self);
        NSLog(@"In Block : %@",self);
    };

二、Block 應用

2.1 Block 應用

1、Block作為變量(Xcode快捷鍵:inlineBlock)

int (^sum) (int, int); // 定義一個 Block 變量 sum
// 給 Block 變量賦值
// 一般 返回值省略:sum = ^(int a,int b)…
sum = ^int (int a,int b){  
    return a+b;
}; // 賦值語句最后有 分號
int a = sum(10,20); // 調(diào)用 Block 變量

2、Block作為屬性(Xcode 快捷鍵:typedefBlock)

// 1. 給  Calculate 類型 sum變量 賦值「下定義」
typedef int (^Calculate)(int, int); // calculate就是類型名
Calculate sum = ^(int a,int b){ 
    return a+b;
};
int a = sum(10,20); // 調(diào)用 sum變量

// 2. 作為對象的屬性聲明,copy 后 block 會轉(zhuǎn)移到堆中和對象一起
@property (nonatomic, copy) Calculate sum;    // 使用   typedef
@property (nonatomic, copy) int (^sum)(int, int); // 不使用 typedef

// 聲明,類外
self.sum = ^(int a,int b){
    return a+b;
};
// 調(diào)用,類內(nèi)
int a = self.sum(10,20);

3、作為 OC 中的方法參數(shù)

// ---- 無參數(shù)傳遞的 Block ---------------------------
// 實現(xiàn)
- (CGFloat)testTimeConsume:(void(^)())middleBlock {
    // 執(zhí)行前記錄下當前的時間
    CFTimeInterval startTime = CACurrentMediaTime();
    middleBlock();
    // 執(zhí)行后記錄下當前的時間
    CFTimeInterval endTime = CACurrentMediaTime();
    return endTime - startTime;

}

// 調(diào)用
[self testTimeConsume:^{
       // 放入 block 中的代碼 

}];

// ---- 有參數(shù)傳遞的 Block ---------------------------
// 實現(xiàn)
- (CGFloat)testTimeConsume:(void(^)(NSString * name))middleBlock {
    // 執(zhí)行前記錄下當前的時間
    CFTimeInterval startTime = CACurrentMediaTime();
    NSString *name = @"有參數(shù)";
    middleBlock(name);
    // 執(zhí)行后記錄下當前的時間
    CFTimeInterval endTime = CACurrentMediaTime();
    return endTime - startTime;
}

// 調(diào)用
[self testTimeConsume:^(NSString *name) {
   // 放入 block 中的代碼,可以使用參數(shù) name
   // 參數(shù) name 是實現(xiàn)代碼中傳入的,在調(diào)用時只能使用,不能傳值    

}];

4、Block回調(diào)
Block回調(diào)是關于Block最常用的內(nèi)容,比如網(wǎng)絡下載,我們可以用Block實現(xiàn)下載成功與失敗的反饋。block使用簡單,邏輯清晰,靈活。

2.2 Block 幾種類型演算

    {
        NSLog(@"\n--------------------block調(diào)用 基本數(shù)據(jù)類型---------------------\n");
        int a = 10;
        NSLog(@"block定義前a地址=%p", &a);
        void (^aBlock)() = ^(){
            NSLog(@"block定義內(nèi)部a地址=%p", &a);
        };
        NSLog(@"block定義后a地址=%p", &a);
        aBlock();
    }
     
    /*
     結果:
     block定義前a地址=0x7fff5bdcea8c
     block定義后a地址=0x7fff5bdcea8c
     block定義內(nèi)部a地址=0x7fa87150b850
     */
     
    /*
     流程:
     1. block定義前:a在棧區(qū)
     2. block定義內(nèi)部:里面的a是根據(jù)外面的a拷貝到堆中的,不是一個a
     3. block定義后:a在棧區(qū)
     */
     
    {
        NSLog(@"\n--------------------block調(diào)用 __block修飾的基本數(shù)據(jù)類型---------------------\n");
         
        __block int b = 10;
        NSLog(@"block定義前b地址=%p", &b);
        void (^bBlock)() = ^(){
            b = 20;
            NSLog(@"block定義內(nèi)部b地址=%p", &b);
        };
        NSLog(@"block定義后b地址=%p", &b);
        NSLog(@"調(diào)用block前 b=%d", b);
        bBlock();
        NSLog(@"調(diào)用block后 b=%d", b);
    }
     
    /*
     結果:
     block定義前b地址=0x7fff5bdcea50
     block定義后b地址=0x7fa873b016d8
     調(diào)用block前 b=10
     block定義內(nèi)部b地址=0x7fa873b016d8
     調(diào)用block后 b=20
     */
     
    /*
     流程:
     1. 聲明 b 為 __block (__block 所起到的作用就是只要觀察到該變量被 block 所持有,就將“外部變量”在棧中的內(nèi)存地址放到了堆中。)
     2. block定義前:b在棧中。
     3. block定義內(nèi)部: 將外面的b拷貝到堆中,并且使外面的b和里面的b是一個。
     4. block定義后:外面的b和里面的b是一個。
     5. block調(diào)用前:b的值還未被修改。
     6. block調(diào)用后:b的值在block內(nèi)部被修改。
     */
     
    {
        NSLog(@"\n--------------------block調(diào)用 指針---------------------\n");
         
        NSString *c = @"ccc";
        NSLog(@"block定義前:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
        void (^cBlock)() = ^{
            NSLog(@"block定義內(nèi)部:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
        };
        NSLog(@"block定義后:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
        cBlock();
        NSLog(@"block調(diào)用后:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
    }
     
    /* 輸出結果
      block定義前:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
      block定義后:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
      block定義內(nèi)部:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x6000002542a0
      block調(diào)用后:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
     c指針本身在block定義中和外面不是一個,但是c指向的地址一直保持不變。
     1. block定義前:c指向的地址在堆中, c指針本身的地址在棧中。
     2. block定義內(nèi)部:c指向的地址在堆中, c指針本身的地址在堆中(c指針本身和外面的不是一個,但是指向的地址和外面指向的地址是一樣的)。
     3. block定義后:c不變,c指向的地址在堆中, c指針本身的地址在棧中。
     4. block調(diào)用后:c不變,c指向的地址在堆中, c指針本身的地址在棧中。
     */
    {
        NSLog(@"\n--------------------block調(diào)用 指針并修改值---------------------\n");
         
        NSMutableString *d = [NSMutableString stringWithFormat:@"ddd"];
        NSLog(@"block定義前:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
        void (^dBlock)() = ^{
            NSLog(@"block定義內(nèi)部:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
            d.string = @"dddddd";
        };
        NSLog(@"block定義后:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
        dBlock();
        NSLog(@"block調(diào)用后:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
    }
     
    /*輸出結果
     block定義前:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8
     block定義后:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8
     block定義內(nèi)部:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x604000253940
     block調(diào)用后:d=dddddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8

     d指針本身在block定義中和外面不是一個,但是d指向的地址一直保持不變。
     在block調(diào)用后,d指向的堆中存儲的值發(fā)生了變化。
     */
     
    {
        NSLog(@"\n--------------------block調(diào)用 __block修飾的指針---------------------\n");
         
        __block NSMutableString *e = [NSMutableString stringWithFormat:@"eee"];
        NSLog(@"block定義前:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
        void (^eBlock)() = ^{
            NSLog(@"block定義內(nèi)部:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
            e = [NSMutableString stringWithFormat:@"new-eeeeee"];
        };
        NSLog(@"block定義后:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
        eBlock();
        NSLog(@"block調(diào)用后:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
    }
     
    /*
     從block定義內(nèi)部使用__block修飾的e指針開始,e指針本身的地址由棧中改變到堆中,即使出了block,也在堆中。
     在block調(diào)用后,e在block內(nèi)部重新指向一個新對象,e指向的堆中的地址發(fā)生了變化。
     */
     
    {
        NSLog(@"\n--------------------block調(diào)用 retain cycle---------------------\n");
         
        View *v = [[View alloc] init];
        v.tag = 1;
        v.frame = CGRectMake(100, 100, 100, 100);
        [self.view addSubview:v];      //self->view->v
        void (^block)() = ^{
            v.backgroundColor = [UIColor orangeColor]; //定義內(nèi)部:block->v
        };
        v.block = block;    //v->block
        block();   
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //預計3秒后釋放v對象。
            [v removeFromSuperview];
        });
    }
     
    /*
     結果:
     不會輸出 dealloc.
     */
     
    /*
     流程:
     1. self->view->v
     2. block定義內(nèi)部:block->v 因為block定義里面調(diào)用了v
     3. v->block
      
     結論:
     引起循環(huán)引用的是block->v->block,切斷其中一個線即可解決循環(huán)引用,跟self->view->v這根線無關
     */
     
    {
        NSLog(@"\n--------------------block調(diào)用self---------------------\n");
         
        View *v = [[View alloc] init];
        v.tag = 2;
        v.frame = CGRectMake(100, 220, 100, 100);
        [self.view addSubview:v];      //self->view->v
        void (^block)() = ^{
            self.view.backgroundColor = [UIColor redColor]; //定義內(nèi)部:block->self
            _count ++;   //調(diào)用self的實例變量,也會讓block強引用self。
             
        };
        v.block = block;    //v->block
        block();
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //預計3秒后釋放self這個對象。
            AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
            appDelegate.window.rootViewController = nil;
        });
    }
    /*
     結果:
     不會輸出 dealloc.
     */
     
    /*
     流程:
     1. self->view->v
     2. v->block
     3. block->self 因為block定義里面調(diào)用了self
      
     結論:
     在block內(nèi)引用實例變量,該實例變量會被block強引用。
     引起循環(huán)引用的是self->view->v->block->self,切斷一個線即可解決循環(huán)引用。
     */

2.3 Block 存儲域

1. 在全局數(shù)據(jù)區(qū)的Block對象

NSGlobalBlock 靜態(tài)block,釋放有兩種不同的時機:

  • 1、如果這個block引用了外部變量后是棧block,則在定義此block的函數(shù)出棧時,block釋放。
  • 2、如果這個blcok引用了外部變量之后是堆block,則其宿主target釋放的時候此block才釋放。
    {
        NSLog(@"\n--------------------block的存儲域 全局塊---------------------\n");
         
        void (^blk)(void) = ^{
            NSLog(@"Global Block");
        };
        blk();
        NSLog(@"%@", [blk class]);
    }
    /*
     結果:輸出 __NSGlobalBlock__
     */
     
    /*
     結論:
     全局塊:這種塊不會捕捉任何狀態(tài)(外部的變量),運行時也無須有狀態(tài)來參與。塊所使用的整個內(nèi)存區(qū)域,在編譯期就已經(jīng)確定。
     全局塊一般聲明在全局作用域中。但注意有種特殊情況,在函數(shù)棧上創(chuàng)建的block,如果沒有捕捉外部變量,block的實例還是會被設置在程序的全局數(shù)據(jù)區(qū),而非棧上。
     */
2.在堆上創(chuàng)建的Block對象

NSMallocBlock 堆區(qū)block
堆區(qū)是內(nèi)存的常駐區(qū)域,也叫永久存儲區(qū),block一般在函數(shù)中定義,最多是個棧block。在MRC時代你需要使用Block_copy()方法,才可以將blcok復制到堆中。

復制到堆中有何用處呢?

  • 首先,作為一個對象,把它復制到堆中,想要使用它肯定要有一個指針指向它,而指向它的指針是作為property或靜態(tài)變量出現(xiàn)的(如果不被引用也就沒有了常駐于堆區(qū)的意義),而實際開發(fā)中多使用poperty引用。
  • 在MRC中,如果一個類的block屬性是使用copy修飾的,則不需要手動調(diào)用Block_copy將其復制到堆中。如果是用strong修飾的,則必須使用Block_copy()將其復制到堆中,并在釋放時調(diào)用Block_release()方法將其釋放,否則會因野指針導致程序崩潰。
    {
        NSLog(@"\n--------------------block的存儲域 堆塊---------------------\n");
         
        int i = 1;
        void (^blk)(void) = ^{
            NSLog(@"Malloc Block, %d", i);
        };
        blk();
        NSLog(@"%@", [blk class]);
    }
    /*
     結果:輸出 __NSMallocBlock__
     */
     
    /*
     結論:
     堆塊:解決塊在棧上會被覆寫的問題,可以給塊對象發(fā)送copy消息將它拷貝到堆上。復制到堆上后,塊就成了帶引用計數(shù)的對象了。
      
     在ARC中,以下幾種情況棧上的Block會自動復制到堆上:
     - 調(diào)用Block的copy方法
     - 將Block作為函數(shù)返回值時(MRC時此條無效,需手動調(diào)用copy)
     - 將Block賦值給__strong修飾的變量時(MRC時此條無效)
     - 向Cocoa框架含有usingBlock的方法或者GCD的API傳遞Block參數(shù)時
      
     上述代碼就是在ARC中,block賦值給__strong修飾的變量,并且捕獲了外部變量,block就會自動復制到堆上。
     */
3.在棧上創(chuàng)建的Block對象

NSStackBlock 棧區(qū)block

  • 函數(shù)只有入棧后才能執(zhí)行,出棧后就釋放了。
  • 棧block一般在函數(shù)內(nèi)部定義,并在函數(shù)內(nèi)部調(diào)用;或者在函數(shù)外部定義,作為函數(shù)的一個參數(shù)在函數(shù)內(nèi)部調(diào)用。函數(shù)出棧時和其他變量或參數(shù)一起釋放。
    {
        NSLog(@"\n--------------------block的存儲域 棧塊---------------------\n");
        int i = 1;
        __weak void (^blk)(void) = ^{
            NSLog(@"Stack Block, %d", i);
        };
        blk();
        NSLog(@"%@", [blk class]);
    }
    /*
     結果:輸出 __NSStackBlock__
     */
     
    /*
     結論:
     棧塊:塊所占內(nèi)存區(qū)域分配在棧中,編譯器有可能把分配給塊的內(nèi)存覆寫掉。
     在ARC中,除了上面四種情況,并且不在global上,block是在棧中。
     */

三、Block 原理

Block 優(yōu)缺點

優(yōu)點:

  • 捕獲外部變量
  • 降低代碼分散程度

缺點:

  • 循環(huán)引用引起內(nèi)存泄露

Block 總結

  • 在block內(nèi)部使用的是將外部變量的拷貝到堆中的(基本數(shù)據(jù)類型直接拷貝一份到堆中,對象類型只將在棧中的指針拷貝到堆中并且指針所指向的地址不變)。
  • __block修飾符的作用:是將block中用到的變量,拷貝到堆中,并且外部的變量本身地址也改變到堆中。
  • __block不能解決循環(huán)引用,需要在block執(zhí)行尾部將變量設置成nil
  • __weak可以解決循環(huán)引用,block在捕獲weakObj時,會對weakObj指向的對象進行弱引用。
  • 使用__weak時,可在block開始用局部__strong變量持有,以免block執(zhí)行期間對象被釋放。
  • 全局塊不引用外部變量,所以不用考慮。
  • 堆塊引用的外部變量,不是原始的外部變量,是拷貝到堆中的副本。
  • 棧塊本身就在棧中,引用外部變量不會拷貝到堆中。
  • __weak 本身是可以避免循環(huán)引用的問題的,但是其會導致外部對象釋放了之后,block 內(nèi)部也訪問不到這個對象的問題,我們可以通過在 block 內(nèi)部聲明一個 __strong 的變量來指向 weakObj,使外部對象既能在 block 內(nèi)部保持住,又能避免循環(huán)引用的問題。
  • __block 本身無法避免循環(huán)引用的問題,但是我們可以通過在 block 內(nèi)部手動把 blockObj 賦值為 nil 的方式來避免循環(huán)引用的問題。另外一點就是 __block 修飾的變量在 block 內(nèi)外都是唯一的,要注意這個特性可能帶來的隱患。
  • block的實現(xiàn)原理是C語言的函數(shù)指針。函數(shù)指針即函數(shù)在內(nèi)存中的地址,通過這個地址可以達到調(diào)用函數(shù)的目的。

Q:什么是Block?
A:Block是將函數(shù)及其執(zhí)行上下文封裝起來的對象

struct __block_impl{
  void *isa;//Block 是對象的標志
  int Flags;
  int Reserved;
  void *FuncPtr;//函數(shù)指針
};

Q:什么是Block調(diào)用?
A:Block調(diào)用是函數(shù)調(diào)用

Q:Block 如何截獲變量?
A:1.基本數(shù)據(jù)類型的局部變量截獲其值
2.對象類型的局部變量連同所有權修飾符一起截獲
3.局部靜態(tài)變量指針形式截獲
4.不截獲全局變量、靜態(tài)全局變量

Q:什么情況使用__block修飾符?
A:一般情況下,對被截獲變量進行賦值操作需要添加__block修飾符
賦值 != 使用
賦值:
賦值操作需要使用 __block修飾

__block NSMutableArray *arrM = nil;
    void (^testBlock)(void) = ^{
        arrM = [NSMutableArray array];
    };
    testBlock();

使用:如下代碼不需要使用__block,因為是對數(shù)組的操作而不是數(shù)組的賦值。

NSMutableArray *arrM = [NSMutableArray array];
    void (^testBlock)(void) = ^{
        [arrM addObject:@"addObj"];
    };
    testBlock();
需要修飾符

不需要修飾符

Q:__block做了什么
A:__block修飾變量變成了對象

__block修飾符

Q:Block的Copy操作效果
A:如圖

Block的Copy操作效果

Q:棧上Block的銷毀
A:如圖

棧上Block的銷毀

Q:棧上Block的Copy操作
A:如圖
Q:棧上Block的Copy,MRC是否會引起內(nèi)存泄漏
A:會的

i棧上Block的Copy

Q:棧上__block變量的Copy操作
A:如圖,修改__block變量值,修改的都是堆上的值

棧上__block變量的Copy操作

Q:__forwarding存在的意義
A:無論在棧還是堆上,__forwarding都可以順利訪問到同一個__block變量

Q:Block外部定義__weak修飾變量可以解決循環(huán)引用?
A:Block截獲對象連同所有權修飾符一起截獲的,如在外部對變量進行__weak修飾,結構體里持有的對象類型也是weak

Q:Block的引用循環(huán),如圖代碼出現(xiàn)什么問題?

A:MRC下,不會產(chǎn)生循環(huán)引用;ARC下回產(chǎn)生循環(huán)引用,引起內(nèi)存泄漏
ARC下的引用循環(huán)

ARC下的引用循環(huán)

ARC下的引用循環(huán)解決方案
解決方案

上述代碼:ARC下解決方案
弊端:如果該Block長時間不被引用,該斷環(huán)處一直存在,循環(huán)引用無法解除

解決方案

Question1:__weak修飾對象,當外部對象釋放了之后,block 內(nèi)部也訪問不到這個對象,怎么辦?

Answer:通過在 block 內(nèi)部聲明一個 __strong 的變量來指向 weakObj,使外部對象既能在 block 內(nèi)部保持住,又能避免循環(huán)引用的問題。

__weak typeof(self) weakSelf = self;
self.block = ^{
      __strong typeof(weakSelf) strongSelf = weakSelf;
      [strongSelf print];
};

我們以 AFNetworking 中 AFNetworkReachabilityManager.m 的一段代碼舉例:

__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
    __strong __typeof(weakSelf)strongSelf = weakSelf;

    strongSelf.networkReachabilityStatus = status;
    if (strongSelf.networkReachabilityStatusBlock) {
        strongSelf.networkReachabilityStatusBlock(status);
    }
};

Question2:__strong修飾對象,會不會引起循環(huán)引用?

Answer:不會!

詳解:

__weak修飾的對象被Block引用,不會影響對象的釋放,而__strong在Block內(nèi)部修飾的對象,會保證,在使用這個對象在scope內(nèi),這個對象都不會被釋放,出了scope,引用計數(shù)就會-1。
self是一個指向?qū)嵗龑ο蟮闹羔?,它的生命周期至少是伴隨著當前的實例對象的,一旦它和對象之間有循環(huán)引用是無法被自動打破的;strongSelf是block內(nèi)部的一個局部變量,變量的作用域僅限于局部代碼,而程序一旦跳出作用域,strongSelf就會被釋放,這個臨時產(chǎn)生的“循環(huán)引用”就會被自動打破,代碼的執(zhí)行事實上也是這樣子的。

推薦閱讀 https://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/

深入篇:iOS-Block本質(zhì)

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容