Objective-C中的Block

.相關概念

在這篇筆記開始之前,我們需要對以下概念有所了解。

1.1 操作系統中的棧和堆

注:這里所說的堆和棧與數據結構中的堆和棧不是一回事。

我們先來看看一個由C/C++/OBJC編譯的程序占用內存分布的結構:

棧區(stack):由系統自動分配,一般存放函數參數值、局部變量的值等。由編譯器自動創建與釋放。其操作方式類似于數據結構中的棧,即后進先出、先進后出的原則。

例如:在函數中申明一個局部變量int b;系統自動在棧中為b開辟空間。

堆區(heap):一般由程序員申請并指明大小,最終也由程序員釋放。如果程序員不釋放,程序結束時可能會由OS回收。對于堆區的管理是采用鏈表式管理的,操作系統有一個記錄空閑內存地址的鏈表,當接收到程序分配內存的申請時,操作系統就會遍歷該鏈表,遍歷到一個記錄的內存地址大于申請內存的鏈表節點,并將該節點從該鏈表中刪除,然后將該節點記錄的內存地址分配給程序。

例如:在C中malloc函數

1charp1;2p1 = (char)malloc(10);

但是p1本身是在棧中的。

鏈表:是一種常見的基礎數據結構,一般分為單向鏈表、雙向鏈表、循環鏈表。以下為單向鏈表的結構圖:

單向鏈表是鏈表中最簡單的一種,它包含兩個區域,一個信息域和一個指針域。信息域保存或顯示關于節點的信息,指針域儲存下一個節點的地址。

上述的空閑內存地址鏈表的信息域保存的就是空閑內存的地址。

全局區/靜態區:顧名思義,全局變量和靜態變量存儲在這個區域。只不過初始化的全局變量和靜態變量存儲在一塊,未初始化的全局變量和靜態變量存儲在一塊。程序結束后由系統釋放。

文字常量區:這個區域主要存儲字符串常量。程序結束后由系統釋放。

程序代碼區:這個區域主要存放函數體的二進制代碼。

下面舉一個前輩寫的例子:

1//main.cpp2inta =0;//全局初始化區3char*p1;//全局未初始化區4main {5intb;//棧6chars[] ="abc";//棧7char*p2;//棧8char*p3 ="123456";//123456\0在常量區,p3在棧上9staticintc =0;//全局靜態初始化區10p1 = (char*)malloc(10);11p2 = (char*)malloc(20);//分配得來的10和20字節的區域就在堆區12strcpy(p1,"123456");//123456\0在常量區,這個函數的作用是將"123456" 這串字符串復制一份放在p1申請的10個字節的堆區域中。13//p3指向的"123456"與這里的"123456"可能會被編譯器優化成一個地址。14}

strcpy函數

原型聲明:extern char *strcpy(char* dest, const char *src);

功能:把從src地址開始且含有NULL結束符的字符串復制到以dest開始的地址空間。

1.2 結構體(Struct)

在C語言中,結構體(struct)指的是一種數據結構。結構體可以被聲明為變量、指針或數組等,用以實現較復雜的數據結構。結構體同時也是一些元素的集合,這些元素稱為結構體的成員(member),且這些成員可以為不同的類型,成員一般用名字訪問。

我們來看看結構體的定義:

structtag { member-list } variable-list;

struct:結構體關鍵字。

tag:結構體標簽。

member-list:結構體成員列表。

variable-list:為結構體聲明的變量列表。

在一般情況下,tag,member-list,variable-list這三部分至少要出現兩個。以下為示例:

1//該結構體擁有3個成員,整型的a,字符型的b,雙精度型的c2//并且為該結構體聲明了一個變量s13//該結構體沒有標明其標簽4struct{5inta;6charb;7doublec;8} s1;9//該結構體擁有同樣的三個成員10//并且該結構體標明了標簽EXAMPLE11//該結構體沒有聲明變量12structEXAMPLE{13inta;14charb;15doublec;16};17//用EXAMPLE標簽的結構體,另外聲明了變量t1、t2、t318structEXAMPLE t1, t2[20], *t3;

以上就是簡單結構體的代碼示例。結構體的成員可以包含其他結構體,也可以包含指向自己結構體類型的指針。結構體的變量也可以是指針。

下面我們來看看結構體成員的訪問。結構體成員依據結構體變量類型的不同,一般有2種訪問方式,一種為直接訪問,一種為間接訪問。直接訪問應用于普通的結構體變量,間接訪問應用于指向結構體變量的指針。直接訪問使用結構體變量名.成員名,間接訪問使用(*結構體指針名).成員名或者使用結構體指針名->成員名。相同的成員名稱依靠不同的變量前綴區分。

1structEXAMPLE{2inta;3charb;4};5//聲明結構體變量s1和指向結構體變量的指針s26structEXAMPLE s1, *s2;7//給變量s1和s2的成員賦值,注意s1.a和s2->a并不是同一成員8s1.a =5;9s1.b =6;10s2->a =3;11s2->b =4;

最后我們來看看結構體成員存儲。在內存中,編譯器按照成員列表順序分別為每個結構體成員分配內存。如果想確認結構體占多少存儲空間,則使用關鍵字sizeof,如果想得知結構體的某個特定成員在結構體的位置,則使用offsetof宏(定義于stddef.h)。

1structEXAMPLE{2inta;3charb;4};5//獲得EXAMPLE類型結構體所占內存大小6intsize_example =sizeof(structEXAMPLE );7//獲得成員b相對于EXAMPLE儲存地址的偏移量8intoffset_b = offsetof(structEXAMPLE, b );

1.3 閉包(Closure)

閉包就是一個函數,或者一個指向函數的指針,加上這個函數執行的非局部變量。

說的通俗一點,就是閉包允許一個函數訪問聲明該函數運行上下文中的變量,甚至可以訪問不同運行上文中的變量。

我們用腳本語言來看一下:

1function funA(callback){2alert(callback());3}4function funB(){5var str ="Hello World";//函數funB的局部變量,函數funA的非局部變量6funA(7function(){8returnstr;9}10);11}

通過上面的代碼我們可以看出,按常規思維來說,變量str是函數funB的局部變量,作用域只在函數funB中,函數funA是無法訪問到str的。但是上述代碼示例中函數funA中的callback可以訪問到str,這是為什么呢,因為閉包性。

2.blcok基礎知識

block實際上就是Objective-C語言對閉包的實現。

2.1 block的原型及定義

我們來看看block的原型:

NSString * ( ^ myBlock )(int);

上面的代碼聲明了一個block(^)原型,名字叫做myBlock,包含一個int型的參數,返回值為NSString類型的指針。

下面來看看block的定義:

1myBlock = ^(intparamA )2{3return[ NSString stringWithFormat:@"Passed number: %i", paramA ];4};

上面的代碼中,將一個函數體賦值給了myBlock變量,其接收一個名為paramA的參數,返回一個NSString對象。

注意:一定不要忘記block后面的分號。

定義好block后,就可以像使用標準函數一樣使用它了:

myBlock(7);

由于block數據類型的語法會降低整個代碼的閱讀性,所以常使用typedef來定義block類型。例如,下面的代碼創建了GetPersonEducationInfo和GetPersonFamilyInfo兩個新類型,這樣我們就可以在下面的方法中使用更加有語義的數據類型。

1//Person.h2#import//Define a new type for the block3typedef NSString * (^GetPersonEducationInfo)(NSString *);4typedef NSString * (^GetPersonFamilyInfo)(NSString *);5@interfacePerson : NSObject6- (NSString *)getPersonInfoWithEducation:(GetPersonEducationInfo)educationInfo7andFamily:(GetPersonFamilyInfo)familyInfo;8@end

我們用一張大師文章里的圖來總結一下block的結構:


2.2 將block作為參數傳遞

1//.h2-(void) testBlock:( NSString * ( ^ )(int) )myBlock;3//.m4-(void) testBlock:( NSString * ( ^ )(int) )myBlock5{6NSLog(@"Block returned: %@", myBlock(7) );7}

由于Objective-C是強制類型語言,所以作為函數參數的block也必須要指定返回值的類型,以及相關參數類型。

2.3 閉包性

上文說過,block實際是Objc對閉包的實現。

我們來看看下面代碼:

1#importvoidlogBlock(int( ^ theBlock )(void) )2{3NSLog(@"Closure var X: %i", theBlock() );4}5intmain(void)6{7NSAutoreleasePool *pool;8int( ^ myBlock )(void);9intx;10pool =[ [ NSAutoreleasePool alloc ] init ];11x =42;12myBlock = ^(void)13{14returnx;15};16logBlock( myBlock );17[ pool release ];18returnEXIT_SUCCESS;19}

上面的代碼在main函數中聲明了一個整型,并賦值42,另外還聲明了一個block,該block會將42返回。然后將block傳遞給logBlock函數,該函數會顯示出返回的值42??即使是在函數logBlock中執行block,而block又聲明在main函數中,但是block仍然可以訪問到x變量,并將這個值返回。

注意:block同樣可以訪問全局變量,即使是static。

2.4 block中變量的復制與修改

對于block外的變量引用,block默認是將其復制到其數據結構中來實現訪問的,如下圖:

通過block進行閉包的變量是const的。也就是說不能在block中直接修改這些變量。來看看當block試著增加x的值時,會發生什么:

1myBlock = ^(void)2{3x++;4returnx;5};

編譯器會報錯,表明在block中變量x是只讀的。

有時候確實需要在block中處理變量,怎么辦?別著急,我們可以用__block關鍵字來聲明變量,這樣就可以在block中修改變量了。

基于之前的代碼,給x變量添加__block關鍵字,如下:

__blockintx;

對于用__block修飾的外部變量引用,block是復制其引用地址來實現訪問的,如下圖:

3.編譯器中的block

3.1 block的數據結構定義

我們通過大師文章中的一張圖來說明:

上圖這個結構是在棧中的結構,我們來看看對應的結構體定義:

1structBlock_descriptor {2unsignedlongintreserved;3unsignedlongintsize;4void(*copy)(void*dst,void*src);5void(*dispose)(void*);6};7structBlock_layout {8void*isa;9intflags;10intreserved;11void(*invoke)(void*, ...);12structBlock_descriptor *descriptor;13/*Imported variables.*/14};

從上面代碼看出,Block_layout就是對block結構體的定義:

isa指針:指向表明該block類型的類。

flags:按bit位表示一些block的附加信息,比如判斷block類型、判斷block引用計數、判斷block是否需要執行輔助函數等。

reserved:保留變量,我的理解是表示block內部的變量數。

invoke:函數指針,指向具體的block實現的函數調用地址。

descriptor:block的附加描述信息,比如保留變量數、block的大小、進行copy或dispose的輔助函數指針。

variables:因為block有閉包性,所以可以訪問block外部的局部變量。這些variables就是復制到結構體中的外部局部變量或變量的地址。

3.2 block的類型

block有幾種不同的類型,每種類型都有對應的類,上述中isa指針就是指向這個類。這里列出常見的三種類型:

_NSConcreteGlobalBlock:全局的靜態block,不會訪問任何外部變量,不會涉及到任何拷貝,比如一個空的block。例如:

1#includeintmain()2{3^{ printf("Hello, World!\n"); } ();4return0;5}

_NSConcreteStackBlock:保存在棧中的block,當函數返回時被銷毀。例如:

1#includeintmain()2{3chara ='A';4^{ printf("%c\n",a); } ();5return0;6}

_NSConcreteMallocBlock:保存在堆中的block,當引用計數為0時被銷毀。該類型的block都是由_NSConcreteStackBlock類型的block從棧中復制到堆中形成的。例如下面代碼中,在exampleB_addBlockToArray方法中的block還是_NSConcreteStackBlock類型的,在exampleB方法中就被復制到了堆中,成為_NSConcreteMallocBlock類型的block:

1voidexampleB_addBlockToArray(NSMutableArray *array) {2charb ='B';3[array addObject:^{4printf("%c\n", b);5}];6}7voidexampleB() {8NSMutableArray *array =[NSMutableArray array];9exampleB_addBlockToArray(array);10void(^block)() = [array objectAtIndex:0];11block();12}

總結一下:

_NSConcreteGlobalBlock類型的block要么是空block,要么是不訪問任何外部變量的block。它既不在棧中,也不在堆中,我理解為它可能在內存的全局區。

_NSConcreteStackBlock類型的block有閉包行為,也就是有訪問外部變量,并且該block只且只有有一次執行,因為棧中的空間是可重復使用的,所以當棧中的block執行一次之后就被清除出棧了,所以無法多次使用。

_NSConcreteMallocBlock類型的block有閉包行為,并且該block需要被多次執行。當需要多次執行時,就會把該block從棧中復制到堆中,供以多次執行。

3.3 編譯器如何編譯

我們通過一個簡單的示例來說明:

1#importtypedefvoid(^BlockA)(void);2__attribute__((noinline))3voidrunBlockA(BlockA block) {4block();5}6voiddoBlockA() {7BlockA block = ^{8//Empty block9};10runBlockA(block);11}

上面的代碼定義了一個名為BlockA的block類型,該block在函數doBlockA中實現,并將其作為函數runBlockA的參數,最后在函數doBlockA中調用函數runBloackA。

注意:如果block的創建和調用都在一個函數里面,那么優化器(optimiser)可能會對代碼做優化處理,從而導致我們看不到編譯器中的一些操作,所以用__attribute__((noinline))給函數runBlockA添加noinline,這樣優化器就不會在doBlockA函數中對runBlockA的調用做內聯優化處理。

我們來看看編譯器做的工作內容:

1#import__attribute__((noinline))2voidrunBlockA(structBlock_layout *block) {3block->invoke();4}5voidblock_invoke(structBlock_layout *block) {6//Empty block function7}8voiddoBlockA() {9structBlock_descriptor descriptor;10descriptor->reserved =0;11descriptor->size =20;12descriptor->copy =NULL;13descriptor->dispose =NULL;14structBlock_layout block;15block->isa =_NSConcreteGlobalBlock;16block->flags =1342177280;17block->reserved =0;18block->invoke =block_invoke;19block->descriptor =descriptor;20runBlockA(&block);21}

上面的代碼結合block的數據結構定義,我們能很容易得理解編譯器內部對block的工作內容。

3.4 copy()和dispose()

上文中提到,如果我們想要在以后繼續使用某個block,就必須要對該block進行拷貝操作,即從??臻g復制到堆空間。所以拷貝操作就需要調用Block_copy()函數,block的descriptor中有一個copy()輔助函數,該函數在Block_copy()中執行,用于當block需要拷貝對象的時候,拷貝輔助函數會retain住已經拷貝的對象。

既然有有copy那么就應該有release,與Block_copy()對應的函數是Block_release(),它的作用不言而喻,就是釋放我們不需要再使用的block,block的descriptor中有一個dispose()輔助函數,該函數在Block_release()中執行,負責做和copy()輔助函數相反的操作,例如釋放掉所有在block中拷貝的變量等。

4.總結

以上內容是我學習各大師的文章后對自己學習情況的一個記錄,其中有部分文字和代碼示例是來自大師的文章,還有一些自己的理解,如有錯誤還請大家勘誤。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容

  • 原文地址:Objective-C中的Block 1.相關概念 在這篇筆記開始之前,我們需要對以下概念有所了解。 1...
    默默_David閱讀 415評論 0 1
  • 1.相關概念 在這篇筆記開始之前,我們需要對以下概念有所了解。 1.1 操作系統中的棧和堆 注:這里所說的堆和棧與...
    DevTalking閱讀 3,740評論 3 76
  • Apple從OS X 10.4和iOS 4以后開始支持block,相對于delegate,block有很多便捷之處...
    HK_Hank閱讀 12,546評論 1 46
  • 2.1 Blcoks概要 2.1.1 什么是Blocks Blocks是C語言的擴充功能——“帶有自動變量(即局部...
    SkyMing一C閱讀 2,366評論 6 18
  • 很多人都渴望有一個從小一起長大,知道彼此這些年發生的所有故事,相互依賴取暖的發小??墒俏铱傉J為出場順序太早,發小只...
    莫兜i閱讀 623評論 0 0