一.目錄
- Block的定義.
一. Block的基礎
1. Block的定義?
Block是一種特殊的數(shù)據(jù)類型,它可以保存一段代碼,在合適的時候取出來調(diào)用.Blocks是C語言的擴充功能, iOS 4中引入了這個新功能“Blocks”。從那開始,Block就出現(xiàn)在iOS的各個API中,并被大家廣泛使用。一句話來形容Blocks,帶有自動變量(局部變量)的匿名函數(shù).
2. Block變量的聲明.
(1)一般的聲明方式
返回值類型(^Block的名稱)(形參) = ^(形參) {};
(2)Block作為函數(shù)的參數(shù)的聲明方式
#import <UIKit/UIKit.h>
typedef void (^Blcok)();
@interface ViewController : UIViewController
- (void)function:(Blcok)block;
@end
3. Block的幾種分類.
(1)無參數(shù)無返回值
// Block的聲明
void (^firstBlock)() = ^() {
NSLog(@"定義一個Block類型(無參數(shù),無返回)");
};
//調(diào)用block
firstBlock();
(2)無參數(shù)有返回值
NSString* (^SecongBlock)() = ^() {
return @"無參數(shù)有返回值";
};
NSString * secongStr = SecongBlock();
NSLog(@"%@",secongStr);
(3)有參數(shù)無返回值
void (^ThirdBlock)(int a, int b) = ^ (int a, int b) {
NSLog(@"sum = %d",a + b);
};
ThirdBlock(1,2);
(4)有參數(shù)有返回值
NSString* (^FourthBlock)(NSString * a, NSString * b) = ^ (NSString * a, NSString * b) {
return [NSString stringWithFormat:@"%@ + %@",a,b];
};
FourthBlock(@"我是",@"好人!");
4. Block的調(diào)用順序.
按照Block的定義來將,Blcok是一段封裝的代碼塊,在合適的時候,也就是說再調(diào)用這段代碼塊的時候才會使用.所以Block的執(zhí)行順序應該是先執(zhí)行調(diào)用語句,再執(zhí)行實現(xiàn)語句.
5. Block的實現(xiàn)原理.
(1)借用這個最簡單的Block來解釋一下.
void (^blk)(void) = ^(){printf("This is a block.");};
blk();
(2)對這個Block進行解析可以得到下面的該Block的源碼.
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("This is a block.");
}
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(){
void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0
,&__main_block_desc_0_DATA);
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
(3) 分析源碼,定義的Block被轉(zhuǎn)換成了
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
可以看到,定義的Block被轉(zhuǎn)換成了指向__main_block_impl_0結(jié)構(gòu)體的指針.先不管構(gòu)造函數(shù)的參數(shù).
先看第一個結(jié)構(gòu)體: __block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
字段描述
1. isa指針,如果我們對runtime了解的話,就明白isa指向Class的指針。
2. Flags,當block被copy時,應該執(zhí)行的操作
3. Reserved為保留字段
4. FuncPtr指針,指向block內(nèi)的函數(shù)實現(xiàn)
__block_impl保存block的類型isa(如&_NSConcreteStackBlock),標識(當block發(fā)生copy時,會用到),block的方法.
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("This is a block.");
}
看第二個結(jié)構(gòu)體: __main_block_desc_0
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)};
字段描述
1. reserved為保留字段默認為0
2. Block_size為sizeof(struct __main_block_impl_0),用來表示block所占內(nèi)存大小。因為沒有持有變量,block大小為impl的大小加上Desc指針大小
3. __main_block_desc_0_DATA為__main_block_desc_0的一個結(jié)構(gòu)體實例
這個結(jié)構(gòu)體,用來描述block的大小等信息。如果持有可修改的捕獲變量時(即加__block),會增加兩個函數(shù)(copy和dispose).
看第三個也是最重要的結(jié)構(gòu)體: __main_block_impl_0
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0里面有兩個變量struct __block_impl_imply和struct __main_block_desc_0.
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
結(jié)構(gòu)體構(gòu)造函數(shù)用來初始化變量`__main_block_impl_0`和`__main_block_desc_0`.
回到開始:
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
我們可以看到,block其實就是指向__main_block_impl_0的結(jié)構(gòu)體指針,這個結(jié)構(gòu)體包含兩個__block_impl和__main_block_desc_0兩個結(jié)構(gòu)體,和一個方法。
通過上面的分析,是不是很已經(jīng)清晰了
最后,main函數(shù)里面的
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
同樣,去除轉(zhuǎn)化代碼,上面的代碼就可以轉(zhuǎn)化為
blk->FuncPtr(bulk);
執(zhí)行block函數(shù)
這樣我們就完成了,對簡單block實現(xiàn)的分析。
該段解釋轉(zhuǎn)載于 沒事蹦蹦作者的<<Block實現(xiàn)原理>> 文章
(http://www.itdecent.cn/p/ca6ac0ae93ad)
6. Block的修飾詞.
(1) __block
說之前,先說說,block訪問對象的問題.



那么__block在這個過程中起到什么作用呢?
- 首先我們發(fā)現(xiàn)圖1中,僅僅訪問對象不進行操作是沒問題的. 但是對該對象操作就會出現(xiàn)報錯.提示:缺少__block修飾.
- 在圖2中,我們發(fā)現(xiàn)在進入block快前后變量c的指針內(nèi)存地址發(fā)生變化了.
- 在圖3中. 第一個模塊. 變量d初始值是2,進入代碼快的時候會被備份一次,再對d進行重新賦值為4,d現(xiàn)在對應的實際值為4了.但是在block塊中還是當d==2時候的備份數(shù)據(jù).所以得出的該dBlock(1,1)的值為4. 但是在第二個模塊中,用__block對e變量修飾了.就會訪問該變量的地址所對應的變量.
總體來說:為了可以讓block可以修改外部的變量,我們需要對變量用__block修飾. 如果block訪問的變量沒有添加這個關鍵字.則會訪問到block自己拷貝的那一份變量,它是在block創(chuàng)建的時候創(chuàng)建的.而訪添加了這個關鍵字的變量,則會訪問這個變量的地址所對應的變量.
(2)__weak / __strong
對對象進行弱引用或者強引用.
在ARC環(huán)境下,我們常常會使用__weak 的修飾符來修飾一個變量,防止其在block中被循環(huán)引用,但是有些特殊情況下,我們在block中又使用__strong 來修飾這個在block外剛剛用__weak修飾的變量,為什么會有這樣奇怪的寫法呢?
在block中調(diào)用self會引起循環(huán)引用,但是在block中需要對weakSelf進行
strong,保證代碼在執(zhí)行到block中,self不會被釋放,當block執(zhí)行完后,
會自動釋放該strongSelf.
7. Block的循環(huán)引用.
說Block的循環(huán)引用之前,先說一下什么叫循環(huán)引用.
循環(huán)引用: 循環(huán)引用可以簡單理解為A引用了B,而B又引用了A,雙方都同時保持對方的一個引用,導致任何時候引用計數(shù)都不為0,始終無法釋放。若當前對象是一個ViewController,則在dismiss或者pop之后其dealloc無法被調(diào)用,在頻繁的push或者present之后內(nèi)存暴增,然后APP就duang地掛了。
Block的循環(huán)引用
block在copy時都會對block內(nèi)部用到的對象進行強引用(ARC)或者retainCount增1(非ARC)。在ARC與非ARC環(huán)境下對block使用不當都會引起循環(huán)引用問題,一般表現(xiàn)為,某個類將block作為自己的屬性變量,然后該類在block的方法體里面又使用了該類本身,block的這種循環(huán)引用會被編譯器捕捉到并及時提醒。
#import "Friend.h"
@interface Friend ()
@property (nonatomic) NSArray *arr;
@end
@implementation Friend
- (id)init
{
if (self = [super init]) {
self.arr = @[@111, @222, @333];
self.block = ^(NSString *name){
NSLog(@"arr:%@", self.arr);
};
}
return self;
}
屬性引用,也就是block里面引用了self導致循環(huán)引用.但是通過實例化變量也是會導致循環(huán)引用.


由此我們知道了,即使在你的block代碼中沒有顯式地出現(xiàn)"self",也會出現(xiàn)循環(huán)引用!只要你在block里用到了self所擁有的東西!具體是這么處理Block的循環(huán)引用:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;//加一下強引用,避免weakSelf被釋放掉
NSLog(@"%@", strongSelf->_xxView); //不會導致循環(huán)引用.
};
__block 本身無法避免循環(huán)引用的問題,但是我們可以通過在 block 內(nèi)部手動把 blockObj 賦值為 nil 的方式來避免循環(huán)引用的問題。另外一點就是 __block 修飾的變量在 block 內(nèi)外都是唯一的,要注意這個特性可能帶來的隱患。
__weak 本身是可以避免循環(huán)引用的問題的,但是其會導致外部對象釋放了之后,block 內(nèi)部也訪問不到這個對象的問題,我們可以通過在 block 內(nèi)部聲明一個 __strong 的變量來指向 weakObj,使外部對象既能在 block 內(nèi)部保持住,又能避免循環(huán)引用的問題。
該段解釋來源于 編程小翁作者的 <<iOS容易造成循環(huán)引用的三種場景,就在你我身邊! >> 文字
(http://www.cnblogs.com/wengzilin/p/4347974.html)
7. Block和引用計數(shù).
在block中訪問對象,默認會retain,而添加了__block的對象不會被retain.
如果我們訪問類的成員變量,或者通過類方法來訪問對象,那么這些對象不會被retain,而類對象會被return,最常見的是self.
根據(jù)這個機制,如果我們將block用來傳值,在block不用時,務必要置為nil,而在實現(xiàn)block的方法里,務必要釋放.
該段解釋轉(zhuǎn)載于 琿少作者的<<iOS 中block結(jié)構(gòu)的簡單用法>> 文章
(https://my.oschina.net/u/2340880/blog/398154)
8. Block的截獲.
block需要注意的一個特性就是"Variable Capturing",直譯過來就是捕捉變量。block會將“捕捉”到的變量復制一份,然后對復制品進行操作,這是非常重要的一點。
二. Blcok的實踐
1. Block處理三級頁面的傳值問題.
需求,頁面A --跳轉(zhuǎn)--> 頁面B --跳轉(zhuǎn)--> 頁面C --返回-- 頁面A. 用頁面C的背景顏色改變頁面A的背景顏色.
- 在頁面C.h聲明一個Block.并在C.m調(diào)用.

2.將控制器C作為B控制器的一個屬性.方便在A控制器中調(diào)用.

3.在控制器A中獲取到控制器C的block屬性.

4.完畢.
2. Block的聲明為一個屬性.需要用copy修飾.
Block屬性的聲明,首先需要用copy修飾符,因為只有copy后的Block才會在堆中,棧中的Block的生命周期是和棧綁定的.