iOS開發之block解析

來源:互聯網
上載者:User

標籤:系統   看到了   clang   原因   命令   time   自身   block實現   printf   

1. block的本質是一個Objective-C的對象。為什麼這麼說?

在Objective-C中。runtime會在執行時依據對象的isa指標的指向,來度額定這個對象的類型。也能夠覺得一個對象。它具有isa指標。就是一個OC對象

2. 你怎麼知道block有isa指標呢。我們能夠通過clang命令將來看block的實現
//測試代碼int main(int argc, const char * argv[]) {    @autoreleasepool {        void(^blk)(void)=^{            NSLog(@"hello lx");        };    }    return 0;}

轉化後:block文法被編譯器轉化成了以下的結構

struct __main_block_impl_0 {  struct __block_impl impl;//block實現的相關資訊  struct __main_block_desc_0* Desc;//block的描寫敘述資訊  __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;  }};

這個結構體由三部分組成impl的結構體(block 對象)+Desc的結構體指標+建構函式,建構函式是用來對結構體做初始化的,剩下的那兩個結構體相應的實現例如以下

//impl的類型,block的相關實現資訊struct __block_impl {  void *isa;//看到了isa指標,說明block是一個isa指標。對於他的類後面會講到  int Flags;//標誌  int Reserved;//  void *FuncPtr;//函數的實現。指向了block所相應的函數的實現地址:由於 block 相當於一個匿名的函數指標};//對於block的描寫敘述資訊struct __main_block_desc_0 {  size_t reserved;  size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
總結一下上面的代碼:

block文法會被編譯器轉化為__main_block_impl_0的結構體。這個結構體由三部分組成:儲存block的相關實現資訊的_block_impl的結構體impl以及儲存block的描寫敘述資訊的__main_block_desc_0的結構體指標Desc以及用來對block做初始化的構造方法。當中impl中的isa的成員說明了block是一個oc對象。另一個一個函數指標指向了block的實現

3. block:事實上相當於c語言中的匿名函數。他能夠訪問外部變數或者對象


  • block這個匿名函數和普通函數的差別

1.沒有函數名,最清純的block ^{ printf("hello"); };,最清純的函數最起碼要有個函數名
2. 帶有脫字元’^’
3.無論c語言還是oc函數都不支援函數嵌套。可是block實現了這個功能。它相當於一個函數。可是他的視線卻是在另外一個函數的內部。
能夠訪問的類型#
  1. 自己主動變數:就是局部變數。訪問局部變數相當於函數調用時的值傳遞,不同意改動:當訪問了外部的變數,block 對象會把他訪問到的自己主動變數作為結構體成員加入到他的結構體當中,並且和原變數同名同值,不能改動的原因是,他們除了值一樣沒有聯絡,相當於值傳遞。上面說到,block相當於一個函數,兩個函數裡面有同樣的的局部變數。一個變了並不會對另外一個產生影響,所以編譯器乾脆就禁止了。實現例如以下
//block訪問外部自己主動變數struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  int a;//將其訪問到的變數加入的自己的結構體中,沒有訪問到的不會加入  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};
#

2 . 靜態變數。在其作用範圍內僅僅有一份記憶體,對其可讀可寫。相當於函數調用時的地址傳遞,block會將它訪問到的外部靜態變數的地址加入到自己的結構體成員中。有了地址,改值又有什麼不能夠呢,實現例如以下

//截獲靜態變數值struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  int *a;//比沒有訪問外部變數時多了這一行,訪問到的外部靜態變數的地址  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};
#

3 . 全域變數:在全域能夠訪問到的。在全域符號表中,與在block外訪問沒有差別,可讀可寫,無論怎樣都能夠訪問到
對於靜態、全域變數的差別,能夠戳這裡我覺得能夠點

#

4 .對象。block能夠讀對象。

#

5 .對象的屬性:可讀可寫

#

6:對於成員變數
self 隱式循環參考:對於一個或者多個成員變數,無論是否由 __block 修飾。block結構體會自己主動產生一個成員 Self。並且會引用成員變數所屬的對象執行個體,self 對於成員變數的改動都是通過對象 self 指標引用來實現的,block 內部對於成員變數的引用也是通過 block 結構體對象的成員 self 指標引用實現(可是會造成循環參考)。

4. 怎樣讓block能夠改動截獲的自己主動變數的值4. 改動block所截獲的外部變數的值

上面說到:block不能夠改動自己主動變數或者對象的指向,這個時候,__block就像一個救世主一樣出如今了我們面前,__block是怎樣實現改動自己主動變數值的,看一下他的實現

struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  __Block_byref_a_0 *a; //多了一個__Block_byref_a_0結構體指標  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};//被block修飾的自己主動變數被這種一個結構體指標儲存了其地址struct __Block_byref_a_0 {  void *__isa;//指向其所相應的類__Block_byref_a_0 *__forwarding;//指向自身 int __flags; int __size; int a;//自己主動變數的值};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  __Block_byref_a_0 *a = __cself->a; // bound by ref            (a->__forwarding->a) = 20;            printf("%d",(a->__forwarding->a));        }

從上面能夠看出,當用__block修飾自己主動變數的時候,這個變數變成了一個struct __Block_byref_a_0的結構體執行個體,這個結構體有一個指向自身的指標。forwarding,來保證這個變數無論在什麼位置都能被正確訪問到。(後面會詳解)
改動變數的過程:impl會依據自己儲存的指向struct __Block_byref_a_0的結構體執行個體的指標,依據這個指標找到farWarding指標找到自己。在找到裡面儲存的自己主動變數,改動他的值。

5. block的儲存域

在前面,我們發現block本質是一個對象。他有一個isa指標,指向其所相應的類,那麼他所相應的類是什麼呢,這就牽扯到block的儲存域了

block的儲存於域有三種類型,當中isa指標指向這三種中的當中一種
1. _NSConcreteStackBlock:儲存在棧上
2. _NSConcreteGlobalBlock:儲存在資料區
3. _NSConcretMallocBlock:儲存在堆上

對於這三種類型的使用方式

_NSConcreteGlobalBlock:
1. 凡是全部的全域block都儲存在資料區
2. 假設block中沒有截獲自己主動變數,block也在資料區

_NSConcreteStackBlock:
1. MRC:預設在棧區,block生命週期與其範圍相關
2.ARC:大多數ARC情況下的block是儲存在堆區的。僅僅有少部分在棧區。在棧區須要我們手動 copy 到堆區
_NSConcretMallocBlock
MRC:通過copy的方法能夠將block從棧區拷貝到堆區
ARC:大多數預設情況在堆區,有棧區的能夠通過copy/strong拷貝到堆區,僅僅要有一個strong指向他,就會被拷貝到堆區

6. 通過copy將block從棧上拷貝到堆上

在棧上的變數的生命週期由編譯器來管理,與其範圍相關聯。出了範圍就會被釋放。可是堆上有程式猿自己管理或者ARC管理,不會由於其出了範圍就被釋放

前面提到了一個forwarding指標,用block修飾的自己主動變數會被儲存在一個__Block_byref_a_0的結構體指標中。這個指標中又有一個指向自己的forwarding,那麼為什麼不直接找到其相應的值,而要通過這個指標呢,由於當block對象被從棧上賦值到堆上的時候。其內部用到的被block修飾的變數也會被賦值一份放在堆上,然後讓棧上forwarding結構體指標指向堆上的結構體就能夠,這樣就能夠保證無論在棧上還是堆上。都能夠正確訪問到變數

ARC 下須要不須要手動copy 的情況
  1. 當 block 被強引用時
  2. 系統的 API 中帶有 usingBlock 時
  3. block 作為函數傳回值

那什麼時候須要我們手動 copy 呢?

當block 作為函數參數的時候,在 arc 下我們自己定義的 block 要寫上 copy。

關於copy的特點

  1. 假設原來在棧上,通過copy。被拷貝到堆上。

  2. 假設原來在全域資料區,不會發生改變
  3. 假設在堆區:其引用計數加1
7. __block在ARC和MRC下的差別

1、 用__block修飾,無論在mrc還是arc下,都能夠改動自己主動變數的值
2、在MRC下會避免循環參考,由於mrc下用__block修飾的變數不會被reatin,所以不會被 block 持有,所以能夠避免循環參考
3、 在ARC下解決循環參考用的是weak:ARC下的循環參考並非有__block引起的,是由於ARC的特性。預設是強引用,所以為瞭解決循環參考,self一般用weak來修飾

8. 為什麼要用copy來修飾block

使用copy能夠將block從棧上轉移到堆上
MRC下,預設是棧上為了控制block生命週期,須要將其copy的堆上。不能夠用reatin取代。

ARC下大多數情況預設是在堆上。可是由於一般遵循傳統,會寫上copy,可是能夠用strong來取代。

iOS開發之block解析

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.