你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> Block解析(iOS)

Block解析(iOS)

編輯:IOS開發綜合

本文為您引見Block解析(IOS)的相關引見,詳細實例請看下文

1. 操作零碎中的棧和堆

我們先來看看一個由C/C++/OBJC編譯的順序占用內存散布的構造:

棧區(stack):由零碎自動分配,普通寄存函數參數值、部分變量的值等。由編譯器自動創立與釋放。其操作方式相似於數據構造中的棧,即後進先出、先進後出的准繩。

例如:在函數中聲明一個部分變量int b;零碎自動在棧中為b開拓空間。

堆區(heap):普通由順序員請求並指明大小,最終也由順序員釋放。假如順序員不釋放,順序完畢時能夠會由OS回收。關於堆區的管理是采用鏈表式管理的,操作零碎有一個記載閒暇內存地址的鏈表,當接納到順序分配內存的請求時,操作零碎就會遍歷該鏈表,遍歷到一個記載的內存地址大於請求內存的鏈表節點,並將該節點從該鏈表中刪除,然後將該節點記載的內存地址分配給順序。

例如:在C中malloc函數

   char p;

   p = (char)malloc(10);

但是p自身是在棧中的。

鏈表:是一種罕見的根底數據構造,普通分為單向鏈表、雙向鏈表、循環鏈表。以下為單向鏈表的構造圖:

Block解析(IOS)

2.2 將block作為參數傳遞

  //.h   -(void)testBlock:(NSString*(^)(int))myBlock;   //.m   -(void)testBlock:(NSString*(^)(int))myBlock   {   NSLog(@"Blockreturned:%@",myBlock(7));   }

由於Objective-C是強迫類型言語,所以作為函數參數的block也必需要指定前往值的類型,以及相關參數類型。

2.3 閉包性

上文說過,block實踐是Objc對閉包的完成。

我們來看看上面代碼:

  #importvoidlogBlock(int(^theBlock)(void))   {   NSLog(@"ClosurevarX:%i",theBlock());   }   intmain(void)   {   NSAutoreleasePool*pool;   int(^myBlock)(void);   intx;   pool=[[NSAutoreleasePoolalloc]init];   x=42;   myBlock=^(void)   {   returnx;   };   logBlock(myBlock);   [poolrelease];     returnEXIT_SUCCESS;   }

下面的代碼在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的值時,會發作什麼:

  myBlock=^(void)   {   x++;   returnx;   };

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

有時分的確需求在block中處置變量,怎樣辦?別焦急,我們可以用__block關鍵字來聲明變量,這樣就可以在block中修正變量了。

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

  __blockintx;

關於用__block修飾的內部變量援用,block是復制其援用地址來完成訪問的.

3.編譯器中的block

3.1 block的數據構造定義

Block解析(IOS)

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

  structBlock_descriptor{   unsignedlongintreserved;   unsignedlongintsize;   void(*copy)(void*dst,void*src);   void(*dispose)(void*);   };   structBlock_layout{   void*isa;   intflags;   intreserved;   void(*invoke)(void*,...);   structBlock_descriptor*descriptor;   /*Importedvariables.*/     };

從下面代碼看出,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。例如:

  #includeintmain()   {   ^{printf("Hello,World!\n");}();   return0;   }

_NSConcreteStackBlock:保管在棧中的block,當函數前往時被銷毀。例如:

  #includeintmain()   {   chara='A';   ^{printf("%c\n",a);}();   return0;   }

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

  voidexampleB_addBlockToArray(NSMutableArray*array){   charb='B';     [arrayaddObject:^{   printf("%c\n",b);     }];     }   voidexampleB(){   NSMutableArray*array=[NSMutableArrayarray];   exampleB_addBlockToArray(array);   void(^block)()=[arrayobjectAtIndex:0];   block();   }

總結:

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

_NSConcreteStackBlock類型的block有閉包行為,也就是有訪問內部變量,並且該block只且只要有一次執行,由於棧中的空間是可反復運用的,所以當棧中的block執行一次之後就被肅清出棧了,所以無法屢次運用。

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

3.3 編譯器如何編譯

  #importtypedefvoid(^BlockA)(void);   __attribute__((noinline))     voidrunBlockA(BlockAblock){   block();   }   voiddoBlockA(){   BlockAblock=^{   //Emptyblock   };   runBlockA(block);   }

下面的代碼定義了一個名為BlockA的block類型,該block在函數doBlockA中完成,並將其作為函數runBlockA的參數,最後在函數doBlockA中調用函數runBloackA。

留意:假如block的創立和調用都在一個函數外面,那麼優化器(optimiser)能夠會對代碼做優化處置,從而招致我們看不到編譯器中的一些操作,所以用__attribute__((noinline))給函數runBlockA添加noinline,這樣優化器就不會在doBlockA函數中對runBlockA的調用做內聯優化處置。

我們來看看編譯器做的任務內容:

  #import__attribute__((noinline))   voidrunBlockA(structBlock_layout*block){   block->invoke();   }     voidblock_invoke(structBlock_layout*block){   //Emptyblockfunction   }   voiddoBlockA(){   structBlock_descriptordescriptor;   descriptor->reserved=0;   descriptor->size=20;   descriptor->copy=NULL;   descriptor->dispose=NULL;   structBlock_layoutblock;   block->isa=_NSConcreteGlobalBlock;   block->flags=12345678;   block->reserved=0;   block->invoke=block_invoke;   block->descriptor=descriptor;   runBlockA(&block);   }

下面的代碼結合block的數據構造定義,我們能很容易得了解編譯器外部對block的任務內容。

3.4 copy()和dispose()

上文中提到,假如我們想要在當前持續運用某個block,就必需要對該block停止拷貝操作,即從棧空間復制到堆空間。所以拷貝操作就需求調用Block_copy()函數,block的descriptor中有一個copy()輔佐函數,該函數在Block_copy()中執行,用於當block需求拷貝對象的時分,拷貝輔佐函數會retain住曾經拷貝的對象。

既然有有copy那麼就應該有release,與Block_copy()對應的函數是Block_release(),它的作用顯而易見,就是釋放我們不需求再運用的block,block的descriptor中有一個dispose()輔佐函數,該函數在Block_release()中執行,擔任做和copy()輔佐函數相反的操作,例如釋放掉一切在block中拷貝的變量等。

【Block解析(iOS)】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved