Objective-C 之Block(1)

Block 語法

Blocks是C語言的擴種功能,是帶有自動變量(局部變量)的匿名函數。數組

^void (int event){
	printf("buttonId: %d event = %d\n", i , event);
}
複製代碼

與通常函數相比,有兩點不一樣bash

  1. 沒有函數名(匿名函數)
  2. 帶有"^"(插入記號)

如下爲Block語法ide

^ 返回值類型 參數列表 表達式函數

如:ui

^int (int count){return count+1;}
複製代碼

能夠省略返回值類型spa

^ 參數列表 表達式設計

省略返回值類型時,若是表達式中有return語句就使用該返回值的類型,若是表達式中沒有人return語句就使用void類型。表達式中含有多個return語句時,全部return的返回值類型必須相同。指針

如:code

^(int count){ return count+1;}
複製代碼

若是不是用參數,參數列表也能夠省略。cdn

^ 表達式

如:

^void (void){ printf("Blocks\n");}
複製代碼

可省略爲:

^{ printf("Blocks\n");}

複製代碼

Block 類型變量

在定義C語言函數時,就能夠將所定義函數的地址賦值給函數指針類型的變量中。

int func(int count)
{
	return count+1;
}

int (*funcptr) (int) = &func;
複製代碼

這樣一來,函數func的地址就能賦值給函數指針類型變量funcptr中了。

注:引用知乎fan wang關於指針的回答,解釋一下指針類型變量原理

做者:fan wang 連接:https://www.zhihu.com/question/31022750/answer/50629732 來源:知乎 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

一圖勝千言1. 聲明變量:C語言聲明一個變量時,編譯器在內存中留出一個惟一的地址單元來存儲變量,以下圖,變量var初始化爲100,編譯器將地址爲1004的內存單元留給變量,並將地址1004和該變量的名稱關聯起來。

2.建立指針:變量var的地址是1004,是一個數字,地址的這個數字能夠用另外一個變量來保存它,假設這個變量爲p,此時變量p未被初始化,系統爲它分配了空間,但值還不肯定,以下圖所示。
3.初始化指針,將變量var的地址存儲到變量p中,初始化後(p=&var),p指向var,稱爲一個指向var的指針。指針是一個變量,它存儲了另外一個變量的地址。
4.聲明指針:typename *p 其中typename指的是var的變量類型,能夠是 short ,char ,float,由於每一個類型佔用的內存字節不一樣,short佔2個字節,char佔1個字節,float佔4個字節,指針的值等於它指向變量的第一個字節的地址 。*是間接運算符,說明p是指針變量,與非指針變量區別開來。 5.*p和var指的是var的內容;p和&var指的是var的地址
6.既然指針*p的值等於var,p的值等於&var,爲何要多發明這一個指針符號增長記憶量呢。指針主要的功能有兩個:避免副本和共享數據。指針的重要功能是函數之間傳遞參數。 talk is cheap, show me the code! 假設用c語言設計一個遊戲,控制人物向前走的函數爲 go_forward(),這個函數接收遊戲人物的座標(int x,int y) 兩個變量,對這兩個變量進行加減操做。

#include <stdio.h> 
void go_forward(int position_x,int position_y)
{  
	position_x=position_x+1;
	position_y=position_y+1;
}
int main()
{
	int x=0;
	int y=0;
	go_forward(x,y);
	printf("當前座標爲:%d,%d \n",x,y);
	return 0;
}
複製代碼

你但願執行go_forward()函數後x,y座標都+1,輸出爲(1,1),可是結果仍是(0,0)緣由爲C語言調用函數的方式是按值傳遞參數,以x參數爲例,剛開始main函數中有一個x的局部變量,值爲0,當計算機調用go_forward()函數時,它將變量x的值複製給了參數position_x,這只是一個賦值過程將變量x賦值給變量position_x,至關於 position_x=x 命令,而這個命令,x的值是不發生變化的,結果以下圖所示,x的值仍爲0,position_x的值變爲1。

解決方法,傳遞指針,用指針告訴go_forward()函數參數x的值的地址,go_forward()函數就能修改對應地址中的內容。因此用指針的主要緣由是讓函數共享存儲器,一個函數能夠修改另外一個函數建立的數據,只要提供數據在內存中的地址,修改代碼以下。

#include <stdio.h>
void go_foward(int *position_x,int*position_y)
{
	*position_x=*position_x+1; 
	*position_y=*position_y+1;
}
int main()
{
	int x=0;
	int y=0;
	go_forward(&x,&y);
	printf("當前座標爲:%d,%d \n",x,y);
	return 0;
}
複製代碼

運行結果:當前座標爲:1,1



一樣地,在Block語法下,能夠將Block語法賦值給生命爲Block類型的變量中。即源代碼中一旦使用Block語法就至關於生成了可賦值給Block類型變量的「值」。

聲明Block類型變量的示例以下:

int (^blk)(int);
複製代碼

與前面的使用函數指針的源代碼對比可知,聲明Block類型變量僅僅是將聲明函數指針類型變量的"*"變爲"^"。該Block類型變量與通常C語言變量徹底相同,可用做:

  • 自動變量(局部變量)
  • 函數參數
  • 靜態變量
  • 靜態全局變量
  • 全局變量
int (^blk)(int) = ^(int count){
	return count+1;
}
複製代碼

由"^"開始的Block語法生成的Block被賦值給變量blk中。由於與一般的變量相同,因此固然也能夠由Block類型變量向Block類型變量賦值。

int (^bkl1)(int) = blk;

int (^blk2)(int);
blk2 = blk1;
複製代碼

在函數參數中使用Block類型變量能夠向函數傳遞Block。

void func(int (^block)(int)){
}
複製代碼

在函數返回值中指定Block類型,能夠將Block做爲函數的返回值返回。

int (^func()(int)){
	return ^(int count){
		return count+1;
		}
}
複製代碼

由此可知,在函數參數和返回值中使用Block類型變量是,記述方式極爲複雜,因此,使用typedef來解決。

typedef int (^blk_t)(int);
複製代碼

如上所示,經過使用typedef可聲明「blk_t」類型變量。

void func(int (^block)(int)){
}
複製代碼

能夠變爲:

void func(blk_t blk){
}
複製代碼
int (^func()(int)){
	return ^(int count){
		return count+1;
		}
}
複製代碼

能夠變爲:

blk_t func(){
	return ^(int count){
		return count+1;
		}
}
複製代碼

另外,將賦值給Block類型變量中的Block方法像C語言一般的函數調用那樣使用,這種方法與使用函數指針類型變量調用函數的方法幾乎徹底相同。 例:變量funcptr爲函數指針類型是,想下面這樣調用函數指針類型變量。

int result = (*funcptr) (10);
複製代碼

變量blk爲Block 類型的狀況下,這樣調用Block類型變量:

int result = blk(10);
複製代碼

在函數參數中使用Block類型變量並在函數中執行Block的例子以下:

int func(blk_t blk, int rate){
	return blk(rate);
}
複製代碼

在OC中也能夠:

- (int) methodUsingBlock:(blk_t) blk rate:(int)rate{
	return blk(rate);
}
複製代碼

Block類型變量可徹底像C語言變量同樣使用,所以也可使用指向Block類型變量的指針,即Block的指針類型變量。

typedef int(^blk_t)(int);

blk_t blk = ^(int count){ return count+1;};

blk_t *blkptr = &blk;

(*blkptr)(10);
複製代碼

截獲自動變量值

int main(){
	int dmy = 256;
	int val = 10;
	const char *fmt = "val = %d\n";
	void (^blk)(void)=^{
		printf(fmt,val);
	};
	
	val = 2;
	fmt = "These values were changed.val=%d\n";
	
	blk();
	
	return 0;
}
複製代碼

運行結果爲:

val = 10
複製代碼

在該源代碼中,Block語法的表達式使用的是它以前聲明的自動變量fmt和val。Block中,Block表達式截獲所使用的自動變量的值,即保存該自動變量的瞬間值。由於Block表達式保存了自動變量的值,因此在執行Block語法後,即便改寫Block中使用的自動變量的值也不會影響Block執行時自動變量的值。

__block 說明符

因爲Block表達式截獲了所使用的自動變量的值,若是在Block中嘗試修改自動變量的值會編譯錯誤:

int val = 0;
void (^blk)(void) = ^{ val = 1; };
blk();
printf("val = %d\n",val);
複製代碼

編譯以後:

Variable is not assignable (missing __block type specifier)
複製代碼

若想在Block語法的表達式中將值賦給在Block語法外聲明的自動變量,須要在該自動變量上附加__block說明符。

上述代碼修改成:

__block int val = 0;
void (^blk)(void) = ^{ val = 1; };
blk();
printf("val = %d\n",val);
複製代碼

運行結果爲:

val = 1
複製代碼

使用附有__block說明符的自動變量可在Block中賦值,該變量成爲__block變量。

截獲的自動變量

若是截獲Objective-C對象,調用變動該對象的方法以下:

id array = [[NSMutableArray alloc] init];

void (^block)(void)=^{
	id obj = [[NSObject alloc] init];
	[array addObject : obj];
};
複製代碼

編譯運行後發現沒有問題,可是向截獲的變量array賦值則會產生編譯錯誤。以上代碼中截獲的變量值爲NSMutableArray類的對象。若是用C語言來描述,便是截獲NSMutableArray類對象用的結構體實例指針。雖然賦值給截獲的自動變量array的操做會產生編譯錯誤,但使用截獲的值缺不會有任何問題。

另外,在使用C語言數組時必須當心使用其指針。

const char text[] = "hello";
void (^blk)(void) = ^{
	printf("%c\n",text[2]);
};
複製代碼

編譯後報錯:

Cannot refer to declaration with an array type inside block
複製代碼

這是由於在如今的Blocks中,截獲自動變量的方法並無實現對C語言數組的截獲,這時候,可使用指針解決該問題。

const char *text = "hello";
void (^blk)(void) = ^{
	printf("%c\n",text[2]);
};
複製代碼
相關文章
相關標籤/搜索