本章教程主要對代碼塊回調模式進行講解,已經分析其餘回調的各類優缺點和適合的使用場景。html
Block塊是封裝工做單元的對象,是能夠在任什麼時候間執行的代碼段。其本質上是可移植的匿名函數,能夠做爲方法和函數的參數傳入,能夠從方法和函數中返回。—(翻譯自官方文檔)java
塊是對C語言的一種擴展,它並未做爲標準的ANSI C所定義的部分,而是有蘋果公司添加到語言中的。塊看起來更像是函數,能夠給塊傳遞參數,塊也能夠具備返回值。編程
蘋果公司在iOS4 SDK中首次支持代碼塊機制,隨後代碼塊機制被普遍應用於各類編碼場景,最多見的爲回調機制,也成爲Block回調。函數
代碼塊也稱Block。是封裝代碼的一種機制,也能夠稱爲匿名函數。編碼
使用這種機制能夠將一段代碼放入一個Block變量中進行存儲,該變量能夠做爲參數進行傳遞,也能夠經過該變量調用其存儲的代碼。atom
在OC語法中,建立一個變量首先要明確其類型。Block做爲一個能夠儲存代碼的變量,其類型相對特殊。spa
肯定block變量的類型有兩個因素:翻譯
只要這兩個因素同樣,咱們就能夠說是相同的block類型。code
如今咱們舉一個簡單的例子,建立一個儲存沒有返回值,沒有輸入參數的代碼的block類型。htm
1
2
|
<code>
void
(^ varBlock)(
void
);
</code>
|
上面的代碼聲明瞭一個block變量,變量名爲varBlock
,其儲存代碼類型爲沒有返回值,沒有輸入參數。
若是想要儲存有返回值,有輸入參數的代碼,一樣能夠聲明響應的block變量進行使用。
1
2
|
<code>
int
(^ varBlock1)(
int
a,
int
b);
</code>
|
上面的代碼聲明瞭一個block變量,變量名爲varBlock1
,其儲存代碼類型爲int型返回值,有兩個int型參數。
Block變量類型較爲複雜,若是直接用這種方式進行聲明變量十分容易儲存。一般咱們用typedef
關鍵字將Block類型重命名,而後用相對簡單的類型名進行聲明變量的工做。
1
2
3
4
|
<code>typedef
void
(^ BlockType1)(
void
);
BlockType1 var1;
//var1與varBlock1爲同一類型
</code>
|
有了Block變量,下面咱們就要給變量賦值。
1
2
3
4
5
6
|
<code>typedef
void
(^ BlockType1)(
void
);
BlockType1 var1;
var1 = ^(){NSLog(@
"test"
)};
</code>
|
經過上述語法格式將代碼封裝在大括號內,並用var1
變量進行儲存。封裝代碼的過程當中要注意一下幾點:
^
符號開始爲Block封裝過程。^
後面的小括號中寫這段代碼須要的參數。該參數有調用者進行賦值。下面舉一個求兩個數的和的代碼封裝過程。
1
2
3
4
5
6
|
<code>typedef
int
(^BlockType)(
int
a,
int
b);
BlockType varBlock;
varBlock = ^(
int
a,
int
b){
return
a+b;};
</code>
|
將代碼存入varBlock
變量中後,即可以使用該變量調用代碼。
1
2
3
4
5
|
<code>
int
a =
4
;
int
b =
6
;
int
sum = varBlock(a,b);
NSLog(@
"sum = %d"
,sum);
//輸出結果爲10
</code>
|
Block變量也能夠給同類型的變量賦值
1
2
3
4
5
|
<code>BlockType varBlockTemp;
varBlockTemp = varBlock;
int
sum = varBlockTemp(
1
,
2
);
NSLog(@
"sum = %d"
,sum);
//輸出結果爲3
</code>
|
將一段代碼當作一個變量進行傳遞,Block這樣的特性極大的方便了咱們以後的編碼工做
經過Block對象將代碼進行封裝的同時,有一個很是關鍵的問題咱們須要明確討論,即Block變量對普通變量做用域的影響。
經過一個簡單案例來所以這個問題。見以下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
|
<code>main()
{
{
int
a =
1
;
{
a =
2
;
}
//此處輸出a的值爲2
}
//此處已經超出變量a的做用域,討論a的值無心義。
}
</code>
|
這段代碼很簡單,經過大擴展來表示變量的做用域範圍。再看下面代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<code>typedef
void
(^ BlockType)(
void
);
BlockType var;
void
fun1()
{
int
a =
10
;
var = ^(){NSLog(@
"a = %d"
,a)};
}
void
fun2()
{
var();
}
main()
{
fun1();
fun2();
}
</code>
|
在fun2函數中調用var
變量時,運行的是fun1中存入var
變量的代碼,且代碼中的使用的變量a
也是fun1中的局部變量。
正常狀態下,變量a
的做用域在fun1函數體大括號內。在函數體大括號外面使用a
是沒有意義的。
但此處狀況特殊,變量a
被block變量var
所使用,因此var
變量將a進行了一個複製操做,也就是咱們在var的代碼裏面使用的a實際上是a的副本。
咱們看下面的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<code>typedef
void
(^ BlockType)(
void
);
BlockType var;
void
fun1()
{
int
a =
10
;
var = ^(){NSLog(@
"a = %d"
,a)};
a =
20
;
}
void
fun2()
{
var();
}
main()
{
fun1();
fun2();
}
</code>
|
這段代碼的輸出和上一段代碼同樣,不會由於fun1函數中a的值發生變化而致使block裏面的a的值發生變化。緣由是Block變量在使用局部變量是,會對局部變量進行一個複製操做,block變量中儲存的代碼使用的時局部變量的副本。
可是在某些特殊場合,咱們須要改變局部變量能夠引發block變量中代碼的變化。這時候咱們須要使用block全景變量。
block全景變量經過:__block
來聲明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<code>typedef
void
(^ BlockType)(
void
);
BlockType var;
void
fun1()
{
__block
int
a =
10
;
var = ^(){NSLog(@
"a = %d"
,a)};
a =
20
;
}
void
fun2()
{
var();
}
main()
{
fun1();
fun2();
}
</code>
|
上文代碼中,fun1中的變量a被block全景變量標識符所修飾,即變量a成爲一個block全景變量。
其做用是,此時block封裝代碼時使用a變量,不會進行復制操做,也就表示,局部變量a與block代碼中的a爲同一個變量。因此,當前代碼的運行結果爲 a = 20。
回調的本質爲控件反饋自身信息,在面向對象編程中,控件須要調用方法反饋自身信息,而方法必須從屬某個對象。因此以前的回調接口必須設置兩個參數,一個反饋對象,一個反饋方法。
在Block回調中,因Block機制能夠直接將代碼封裝如一個變量中,並且這個變量能夠當作參數進行傳遞。利用這個機制,組件能夠保存這段代碼,在觸發事件的時候直接調用此段代碼,不須要設置反饋對象和反饋方法。
這裏仍然用以前的開關最爲例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<code>typedef
enum
: NSUInteger {
SwitchStateOff,
//default
SwitchStateOn,
} SwitchState;
typedef
void
(^SBlockType)(SwitchState state);
@interface
SwitchB : NSObject
@property
(nonatomic,assign,readonly)SwitchState currentState;
@property
(nonatomic,strong)SBlockType changeStateBlockHandle;
@end
</code>
|
聲明中的changeStateBlockHandle
屬性就是保存回調代碼。設置回調,只須要將此屬性賦值就可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<code>
@interface
Room : NSObject
@property
(strong, nonatomic) Light *lightA;
@property
(strong, nonatomic) SwitchB *s;
@end
@implementation
Room
- (instancetype)init
{
self = [
super
init];
if
(self) {
self.lightA = [[Light alloc] init];
self.s = [[SwitchB alloc] init];
__weak __block Room * copy_self = self;
//打破強引用循環,後續章節會展開講解
self.s.changeStateBlockHandle = ^(SwitchState state)
{
if
(state == SwitchStateOff)
{
[self.lightA turnOff];
}
else
{
[self.lightA turnOn];
}
};
}
return
self;
}
@end
</code>
|
當開關的狀態發生改變時,開關須要將自身狀態反饋給使用者。當使用Block回調接口的組件時,須要將回調代碼直接封裝,賦值給組件響應的Block類型的屬性便可。當狀態改變時,封裝的代碼便被組件調用。