Block 初探

在介紹Block之前通過一個簡單的應(yīng)用場景認識下Block

場景描述如下:TableView上面有多個CustomTableViewCell沫换,cell上面顯示的是文字信息和一個詳情Button沦寂,點擊button后push到一個新的頁面。

對于這個簡單的需求: 點擊button以實現(xiàn)頁面跳轉(zhuǎn)枕面。button添加在CustomTableViewCell上锰镀,若直接在cell中實現(xiàn)跳轉(zhuǎn)邏輯超棺,就違背MVC的計模式。項目小還好侦讨,隨著功能的擴展,你會發(fā)現(xiàn)越寫越難寫苟翻;還有一種情況韵卤,就是這件事情做不到,只能委托給其他對象來做了崇猫,此時使用delegate委托是個很好的選擇沈条。

下面先用delegate實現(xiàn),代碼如下:
CustomTableViewCell 類中

@protocol CustomTableViewCellDelegate <NSObject>
- (void) pushToNewView ; //代理要實現(xiàn)的方法
@end

@interface CustomTableViewCell : UITableViewCell

@property(nonatomic,strong) UIButton *detailButton;
@property(nonatomic, weak) id<CustomCellDelegate> delegate;

@end

CustomTableViewCell 添加協(xié)議诅炉,并聲明要代理要實現(xiàn)的方法蜡歹。

接下來在CustomTableViewCell.m中為button添加點擊事件,
button的 click 事件如下:

- (void)buttonClick:(UIButton *)button {
  if (_delegate && [_delegate respondsToSelector:@selector(pushToNewView)]) {
     [_delegate pushToNewView];
  }
}

跟著就是完善受到委托申請的類涕烧,這里是對應(yīng)CustomTableViewCell所在的控制器月而,首先遵循CustomTableViewCellDelegate協(xié)議,然后要實現(xiàn)其中的pushToNewView方法议纯,最重要的是設(shè)置CustomTableViewCell對象cell的delegate等于self父款。

@interface ViewController ()< CustomTableViewCellDelegate >
@property (nonatomic, strong) NSArray *textArray;
@end

實現(xiàn) pushToNewView的方法,

- (void) pushToNewView {
    DetailViewController*detailVC = [[DetailViewController alloc] init];
    [self.navigationController pushViewController:detailVC animated:YES];
}

同時需要設(shè)置 CustomTableViewCell 對象cell的delegate痹扇,在 cellForRow中實現(xiàn) cell.delegate = self即可铛漓。

此時self.delegate其實就是ViewController,cell對象委托了ViewController實現(xiàn)pushToNewVC方法鲫构。這個簡單的場景描述了使用代理的一種情況浓恶,就是CustomTableViewCell沒有能力實現(xiàn)pushViewController的功能,所以委托ViewController來實現(xiàn)结笨。

------------------- 分割線 --------------------------

接著用 Block 實現(xiàn)上述功能
根據(jù)需求:定義一個無參無返的Block, 簡化代碼使用 typedef
typedef void(^ButtonCallback)(void);
同時需要在CustomTableViewCell類中添加block屬性
@property (nonatomic, copy) ButtonCallback buttonCallback;

在button 點擊事件中包晰,調(diào)用剛剛聲明的 buttonCallback

- (void)buttonClick:(UIButton *)button {
  if(self.buttonCallback){
     self.buttonCallback();
  }
}

最后我們回到CustomTableViewCell所在的控制器中湿镀,去實現(xiàn) buttonCallback
找到cellForRow的方法,通過cell點出buttonCallback的屬性伐憾,并實現(xiàn)

cell.buttonCallback = ^{
    DetailViewController*detailVC = [[DetailViewController alloc] init];
    [self.navigationController pushViewController:detailVC animated:YES];
};

兩種方式對比勉痴,Block 要比 delegate 精簡很多.

下面認識下 OC 中的 Block

1. 閉包 (了解)

在計算機科學(xué)中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱树肃,是引用了自由變量的函數(shù)蒸矛。這個被引用的自由變量將和這個函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外胸嘴。所以雏掠,有另一種說法認為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實體。 ---- 維基百科

閉包就是一個函數(shù)劣像,或者一個指向函數(shù)的指針乡话,加上這個函數(shù)執(zhí)行的非局部變量。
說的通俗一點耳奕,就是閉包允許一個函數(shù)訪問聲明該函數(shù)運行上下文中的變量绑青,甚至可以訪問不同運行上文中的變量。

2.Block 基本語法及使用

Block實際上是OC語言對閉包的實現(xiàn)屋群,是帶有自動變量值的匿名函數(shù)

2.1 Block的原型及定義

Block語法及常見類型

^ 返回值類型 參數(shù)列表 表達式

其中闸婴,返回值類型可省略

^ 參數(shù)列表 表達式

當沒有參數(shù)時,可省略參數(shù)列表

^表達式

Block基本語法
Block 常見類型

  1. 有參有返回值
NSString(^testBlock)(int) = ^(int num){
      return [NSString stringWithFormat:"%d",num];
};
  1. 有參無返回值
void(^testBlock)(int) = ^(int num){
      NSLog(num = "%d",num);
};
  1. 無參無返回值
void(^testBlock)(void)= ^{
      NSLog(@"無參無返回值");
};
  1. 無參有返回值
int(^testBlock)(void) = ^{
      return 10;
};

下面我們簡單使用下Block:
先寫一個Block原型 (有參有返回值)
NSString *(^myBlock)(int)

下面看下Block的實現(xiàn)部分

myBlock = ^(int num){
    return [NSString stringWithFormat: @"Passed number: %d", num];
};

代碼中將一個函數(shù)體賦值給了myBlock變量谓晌,其接收一個名為num一個NSString對象掠拳。

Block最后的分號一定不能忘

至于調(diào)用
myBlock(3) 可以像調(diào)用其他函數(shù)一樣使用Block。

代碼簡化:
由于block數(shù)據(jù)類型的語法會降低整個代碼的閱讀性纸肉,所以常使用typedef來定義block類型溺欧。
typedef NSString(^MyBlock)(int);
上述重定義后構(gòu)建了MyBlock新類型,這樣我們就可以在屬性聲明或方法中使用更加有語義的數(shù)據(jù)類型柏肪。

下圖很好的總結(jié)Block結(jié)構(gòu)
Block.jpg
2.2 Block的使用 (代碼為例)
  1. 界面?zhèn)髦?/li>
  2. 回調(diào)
3. Block對外部變量的截獲

3.1 局部變量
局部自動變量姐刁,在Block中可被讀取。Block定義時copy變量的值烦味,在Block中作為常量使用聂使,所以即使變量的值在Block外改變,也不影響他在Block中的值谬俄,Block此時對局部變量只是做了值傳遞的操作柏靶。

3.2 static 修飾的全局變量
因為全局變量或靜態(tài)變量在內(nèi)存中的地址是固定的,Block在讀取該變量值的時候是直接從其所在內(nèi)存地址讀出溃论,獲取到的是最新值屎蜓,而不是在定義時copy的常量。

3.3 對OC對象的截獲

     NSMutableArray *array = [NSMutableArray array];     
     void(^block)() = ^(){       
         NSObject *obj = [[NSObject alloc] init];    
         [array addObject:obj];         
     };   
     block();

上述代碼編譯通過钥勋,Block截獲的值為NSMutableArray類的對象炬转,用C語言表述辆苔,就是用的NSMutableArray類的對象所用的結(jié)構(gòu)體實例的指針,所以向該對象中添加元素操作屬于使用截獲變量的值扼劈,因此是沒有問題的驻啤。那么對該截獲的變量進行賦值

屏幕快照 2016-06-23 下午2.12.28.png

編譯未通過,提示缺少__block修飾符荐吵。

3.4 C語言數(shù)組

屏幕快照 2016-06-23 下午2.18.28.png

上圖代碼中, 在Block外部定義一個C語言字符串字面量數(shù)組, 在Block內(nèi)部截獲自動變量的方法并沒有實現(xiàn)對C語言數(shù)組的截獲, 此時訪問數(shù)組元素text[2]會報錯. 此時使用指針可以解決.

 const char *text = "adsdczv";
 void(^block)() = ^(){
       NSLog(@"%c",text[2]);
 };
 block();

3.5 ______block 修飾的變量
某些場景下骑冗,我們需要在Block內(nèi)部對外部變量進行修改。這時需要使用__block來修飾該變量實現(xiàn)在Block內(nèi)部的修改先煎,此時Block是復(fù)制其引用地址來實現(xiàn)訪問的沐旨。

關(guān)于______block 修飾符
從上面講解我們已經(jīng)知道,Block內(nèi)部能夠讀取外部局部變量的值榨婆。但如果我們需要在Block內(nèi)部修改變量的值,則需要在Block外部給該變量添加一個__block修飾符褒侧。
__block另一個使用場景是良风,避免某些情況下Block使用中出現(xiàn)的循環(huán)引用的問題,此時可以給相應(yīng)的對象加上一個__block來修飾闷供。

為什么使用__block可以實現(xiàn)在Block內(nèi)部修改外部變量的值烟央?

這邊我們用一個Block代碼,并使用clang _rewrite_objc命令轉(zhuǎn)換成C++的代碼來說明__block是怎么實現(xiàn)內(nèi)部變量的修改歪脏。
Block在main中實現(xiàn)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSInteger val = 10;
        void (^block)(void) = ^{
            NSLog(@"%ld", val);
        };
        block();
    }
    return 0;
}

轉(zhuǎn)碼后:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct  __main_block_impl_0 *__cself) {
  int val = __cself->val;  // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__val_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_41daf1_mi_0, val);
}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int val = 10;
        void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val);
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

從展開代碼可以發(fā)現(xiàn)疑俭,Block被轉(zhuǎn)成了一個struct __main_block_impl_0類型的結(jié)構(gòu)體實例,并且該結(jié)構(gòu)體成員中包含局部變量val婿失。當執(zhí)行Block時钞艇,通過該實例找到Block執(zhí)行部分void __main_block_func_0,并把該結(jié)構(gòu)體實例傳入到void __main_block_func_0方法中豪硅。
void __main_block_func_0方法中第一個參數(shù)聲明如下
struct __main_block_impl_0 *__cself
*注意:這里的__cself就類似于OC中的self, * 而它指向結(jié)構(gòu)體的指針哩照。
此時我們就可以通過__cself->val 訪問該局部變量。

那么問題來了懒浮,為什么此時不對變量val進行修改飘弧?

因為main函數(shù)中的局部變量val和函數(shù)__main_block_func_0不在同一個作用域中,調(diào)用過程中只是進行了值傳遞砚著。
當然次伶,在上面代碼中,我們可以通過指針來實現(xiàn)局部變量的修改稽穆。不過這是由于在調(diào)用__main_block_func_0時冠王,main函數(shù)棧還沒展開完成,變量val還在棧中秧骑。
但是在很多情況下版确,Block是作為參數(shù)傳遞以供后續(xù)回調(diào)執(zhí)行的扣囊。通常在這些情況下,Block被執(zhí)行時绒疗,定義時所在的函數(shù)棧已經(jīng)被展開侵歇,局部變量已經(jīng)不在棧中了,再用指針訪問會產(chǎn)生野指針錯誤吓蘑。
所以惕虑,這類情況下對于auto類型的局部變量,不允許Block進行修改是合理的磨镶。

__block是如何實現(xiàn)變量修改的

此時使用更新后的代碼

添加 __block修飾符后

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSInteger val = 0;
        void (^block)(void) = ^{
            val = 1;
        };
        block();
        NSLog(@"val = %ld", val);
    }
    return 0;
}

使用_rewrite_objc展開

struct __Block_byref_val_0 {
   void *__isa;
   __Block_byref_val_0 *__forwarding;
   int __flags;
   int __size;
   NSInteger val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

            (val->__forwarding->val) = 1;

        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 0};

        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2h_70k4gzp53qn7ytk0cdjr9kk80000gn_T_main_7eb9e7_mi_0,(val.__forwarding->val));

    }
    return 0;
}

這次轉(zhuǎn)碼后似乎比剛才多了些東西溃蔫,仔細看下,
一個是__Block_byref_val_0的結(jié)構(gòu)體以及兩個方法static void __main_block_copy_0static void __main_block_dispose_0; 后面的兩個方法先暫且不關(guān)注(后面會涉及)琳猫。

其實結(jié)構(gòu)體__Block_byref_val_0產(chǎn)生的實例就是我們使用__block修飾過的變量伟叛。

struct __Block_byref_val_0 {
   void *__isa;
   __Block_byref_val_0 *__forwarding;
   int __flags;
   int __size;
   NSInteger val;
};

從該結(jié)構(gòu)體聲明可以看出,這個結(jié)構(gòu)體中包含了該實例本身的引用 __forwarding脐嫂。

  • 我們從上述被轉(zhuǎn)化的代碼中可以看出 Block 本身也一樣被轉(zhuǎn)換成了 __main_block_impl_0 結(jié)構(gòu)體實例统刮,該實例持有__Block_byref_val_0結(jié)構(gòu)體實例的指針。

我們再看一下Block實現(xiàn)和調(diào)用部分代碼被轉(zhuǎn)化后的結(jié)果:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
            (val->__forwarding->val) = 1;
        }

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  • 不難發(fā)現(xiàn)從__cself找到__Block_byref_val_0結(jié)構(gòu)體實例账千,然后通過該實例的__forwarding訪問成員變量val侥蒙。成員變量val是該實例自身持有的變量,指向的是原來的局部變量匀奏。

詳情參見下圖:

__block.jpg

至此鞭衩,已經(jīng)展示了__block變量在Block中查找和修改的過程,那么:

  • 當Block作為回調(diào)執(zhí)行時娃善,局部變量val已經(jīng)出棧了论衍,這個時候代碼為什么還能正常工作呢?
  • 我們?yōu)槭裁赐ㄟ^成員變量__forwarding而不是直接去訪問結(jié)構(gòu)體中我們需要修改的變量呢?
這邊需要引入下一個概念: 存儲域

Objective-C中Block的存儲域

我們在上述轉(zhuǎn)換過的代碼中可以發(fā)現(xiàn) __main_block_impl_0結(jié)構(gòu)體構(gòu)造函數(shù)中,isa指針指向的是 _NSConcreteStackBlock; 而Block還有另外兩個與之相似的類:

  • _NSConcreteGlobalBlock //全局的靜態(tài)block 不會訪問任何外部變量
  • _NSConcreteMallocBlock //保存在堆區(qū)的会放,引用計數(shù)為0時會被銷毀饲齐。
  • _NSConcreteStackBlock //保存在棧區(qū),出棧后被銷毀

上述示例代碼中咧最,Block是被設(shè)為_NSConcreteStackBlock捂人,在棧上生成。當我們把Block作為全局變量使用時矢沿,對應(yīng)生成的Block將被設(shè)為_NSConcreteGlobalBlock

void (^block)(void) = ^{NSLog(@"This is a Global Block");};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        block();
    }
    return 0;
}

該代碼轉(zhuǎn)碼c++后滥搭,Block結(jié)構(gòu)體的isa指針初始化時如下:
impl.isa = &_NSConcreteGlobalBlock;

_NSConcreteMallocBlock何時被使用

分配在全局變量上的Block捣鲸,在變量作用域外也可以通過指針安全的訪問瑟匆。
但分配在棧上的Block,如果它所屬的變量作用域結(jié)束栽惶,該Block就被廢棄愁溜。同樣疾嗅,__block變量也分配在棧上,當超過該變量的作用域時冕象,該__block變量也會被廢棄代承。
此時,就需要使用 _NSConcreteMallocBlock渐扮,OC中提供了將Block和__block變量從棧上復(fù)制到堆上的方法论悴,將分配到棧上的Block復(fù)制到堆上,這樣當棧上的Block超過它原本作用域時墓律,堆上的Block還可以繼續(xù)存在膀估。
復(fù)制到堆上的Block,它的結(jié)構(gòu)體成員變量isa將變?yōu)椋?br> impl.isa = &_NSConcreteMallocBlock耻讽;

_block變量中結(jié)構(gòu)體成員__forwarding就在此時保證了從棧上復(fù)制到堆上能夠正確訪問__block變量察纯。在這種情況下,只要棧上的_block變量的成員變量__forwarding指向堆上的實例针肥,我們就能夠正確訪問捐寥。

我們一般可以使用copy方法手動將 Block 或者 __block變量從棧復(fù)制到堆上。比如我們把Block做為類的屬性訪問時祖驱,我們一般把該屬性設(shè)為copy。有些情況下我們可以不用手動復(fù)制,比如Cocoa框架中使用含有usingBlock方法名的方法時瞒窒,或者GCD的API中傳遞Block時捺僻。

當一個Block從棧復(fù)制到堆中,與之相關(guān)的__block變量也會被復(fù)制到堆中崇裁。此時堆中的Block持有相應(yīng)堆上的__block變量匕坯,當堆上的__block變量沒有持有者,才會被釋放拔稳。

  • 而在棧上的__block變量被復(fù)制到堆上之后葛峻,會將成員變量__forwarding的值替換為堆上的__block變量的地址。這個時候我們可以通過以下代碼訪問:
    val.__forwarding->val
    如下圖:
__block變量和循環(huán)引用問題

__block修飾符可以指定任意類型的局部變量巴比。此時還記這兩個方法嗎术奖?

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src {
    _Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}

當Block從棧復(fù)制到堆時,會調(diào)用_Block_object_assign函數(shù)持有該變量(相當于retain);
當堆上的Block被廢棄時轻绞,會調(diào)用_Block_object_dispose函數(shù)釋放該變量(相當于release)采记。

由上文描述可知,我們可以使用下述代碼解除Block循環(huán)引用的問題:

__block id tmp = self;
void(^block)(void) = ^{
    tmp = nil;
};

block();

通過執(zhí)行block方法掰读,nil被賦值到_block變量tmp中背稼。這個時候_block變量對 self 的強引用失效吊洼,從而避免循環(huán)引用的問題磅氨。

*總結(jié):
通過__block變量可以控制對象的生命周期既棺,在不能使用__weak修飾符的環(huán)境中讽挟,我們可以避免使用__unsafe_unretained修飾符。
在執(zhí)行Block時可動態(tài)地決定是否將nil或者其它對象賦值給__block變量丸冕。
但是這種方法有一個明顯的缺點就是耽梅,我們必須去執(zhí)行Block才能夠解除循環(huán)引用問題,否則就會出現(xiàn)問題晨仑。

4. 比較 ______weak 和 __strong

這邊用AFN中的一段代碼

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

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

我們在使用Block時褐墅,有時候會用到self,而Block內(nèi)部對self默認都是強引用洪己。在ARC下妥凳,編譯器將Block從棧區(qū)拷貝到堆區(qū),Block會強引用和持有self答捕,而self 也會強引用和持有Block逝钥,于是就造成了循環(huán)引用。

此時就需要使用__weak拱镐,在修飾變量時艘款,修飾符修飾變量 self,讓 block 不強引用 self沃琅,從而破除循環(huán)哗咆。

    __weak typeof(self) weakSelf = self;
        self.passValueBlock = ^(NSString *string){      
            dispatch_async(dispatch_get_main_queue(), ^{    
                weakSelf.pointView.startLabel.text = string;        
            });        
        };

弱引用不會影響對象釋放,當一個對象被釋放是益眉,所有指向它的弱引用會被置空晌柬,也避免出現(xiàn)野指針。

2. __strong

上面提到郭脂,__weak 很好的解決retain Cycle年碘,但還是會存在一些隱患。不知道self什么時候被釋放展鸡,為了保證在Block內(nèi)部不會被釋放屿衅,所以使用__strong修飾。

看下一段測試代碼
ViewController添加屬性
@property (nonatomic, strong) ViewController *vc;
viewDidLoad

    ViewController *vc = [[ViewController alloc] init];
    self.vc = vc;
    __weak ViewController * weakVC = self.vc;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSInteger count = 0;  
        while (count < 4) {   
            count++;
            NSLog(@"%@",weakVC);     
            sleep(1);
        }    
    });
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        self.vc = nil;
    });

實現(xiàn)dealloc方法

- (void)dealloc { 
    NSLog(@"%@",[self class]);
}

看輸出結(jié)果:

2016-06-20 15:12:27.797 __strongTest[14823:1753981] <ViewController: 0x7fdbd2724c20>
2016-06-20 15:12:28.802 __strongTest[14823:1753981] <ViewController: 0x7fdbd2724c20>
2016-06-20 15:12:29.797 __strongTest[14823:1753934] ViewController
2016-06-20 15:12:29.804 __strongTest[14823:1753981] (null)
2016-06-20 15:12:30.808 __strongTest[14823:1753981] (null)

可以看出:Block內(nèi)部的對self.vc是弱引用莹弊。當2s后涤久,self.vc在外部被釋放,則Block內(nèi)部對self.vc的持有也失效忍弛。

現(xiàn)在在Block內(nèi)部對self.vc進行強引用拴竹,Block內(nèi)部代碼調(diào)整為:

   __strong ViewController *strongVC = weakVC;
   NSInteger count = 0;
   while (count < 4) {
       count++;
       NSLog(@"%@",strongVC);
       sleep(1);
   }

再看輸出結(jié)果:

2016-06-20 15:22:38.423 __strongTest[14839:1762881] <ViewController: 0x7fd632d1f690>
2016-06-20 15:22:39.424 __strongTest[14839:1762881] <ViewController: 0x7fd632d1f690>
2016-06-20 15:22:40.429 __strongTest[14839:1762881] <ViewController: 0x7fd632d1f690>
2016-06-20 15:22:41.430 __strongTest[14839:1762881] <ViewController: 0x7fd632d1f690>
2016-06-20 15:22:42.431 __strongTest[14839:1762835] ViewController

Block內(nèi)部對對象采用strong修飾后,既使原持有對象在block外部已經(jīng)被釋放剧罩,但Block內(nèi)部扔能持有栓拜,于是執(zhí)行完Block后,該對象才被dealloc。

總結(jié):weakSelf是為了Block不持有self幕与,避免循環(huán)引用挑势,而再聲明一個strongSelf是因為一旦進入Block執(zhí)行,就不允許self在這個執(zhí)行過程中釋放啦鸣。Block執(zhí)行完后這個strongSelf會自動釋放潮饱,沒有循環(huán)引用問題。

最后诫给,使用Block時的注意事項

1.Block內(nèi)部不能直接修改局部變量
Block內(nèi)部可以訪問外部的變量, 默認是將其拷貝到其數(shù)據(jù)結(jié)構(gòu)中來實現(xiàn)訪問的, 屬性是只讀的. Block內(nèi)部不能修改外面的局部變量.
如果要修改需要對要修改的局部變量用__block 修飾, 這樣局部變量就可以在Block內(nèi)部修改了香拉,Block是復(fù)制其引用地址來實現(xiàn)訪問的

2.當Block里面的出現(xiàn)self,造成的循環(huán)引用
循環(huán)引用就是當self 擁有一個Block的時候,在Block中又調(diào)用self的方法中狂。形成了你中有我凫碌,我中有你,造成誰都無法將誰釋放胃榕。從而發(fā)生內(nèi)存泄漏盛险。
解決方法:
__weak typeof (self) weakSelf = self;
定義一個weakSelf變量并加上__weak修飾符,在Block代碼塊中勋又,所有需要self的地方都用weakSelf來替代苦掘。這樣就不會增加引用計數(shù),所以Block持有self對象也就不會造成循環(huán)引用楔壤,從而避免內(nèi)存泄漏鹤啡。

參考

Objective-C中的Block
閉包(Closures)
Objective-C中Block的存儲域
______block & ______weak & __strong
Objective-C 高級編程: iOS和OS X多線程和內(nèi)存管理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蹲嚣,隨后出現(xiàn)的幾起案子揉忘,更是在濱河造成了極大的恐慌,老刑警劉巖端铛,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異疲眷,居然都是意外死亡禾蚕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門狂丝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來换淆,“玉大人,你說我怎么就攤上這事几颜”妒裕” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵蛋哭,是天一觀的道長县习。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么躁愿? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任叛本,我火速辦了婚禮,結(jié)果婚禮上彤钟,老公的妹妹穿的比我還像新娘来候。我一直安慰自己,他們只是感情好逸雹,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布营搅。 她就那樣靜靜地躺著,像睡著了一般梆砸。 火紅的嫁衣襯著肌膚如雪转质。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天辫樱,我揣著相機與錄音峭拘,去河邊找鬼。 笑死狮暑,一個胖子當著我的面吹牛鸡挠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搬男,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼拣展,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了缔逛?” 一聲冷哼從身側(cè)響起备埃,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎褐奴,沒想到半個月后按脚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡敦冬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年辅搬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脖旱。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡堪遂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出萌庆,到底是詐尸還是另有隱情溶褪,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布践险,位于F島的核電站猿妈,受9級特大地震影響吹菱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜于游,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一毁葱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贰剥,春花似錦倾剿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至担忧,卻和暖如春芹缔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓶盛。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工最欠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惩猫。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓芝硬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親轧房。 傳聞我的和親對象是個殘疾皇子拌阴,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

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