成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

iOS開發:block的探究

移動開發 iOS
一個函數里定義了個block,這個block可以訪問該函數的內部變量。本文就來介紹一下iOS開發中的

​[0. Brief introduction of block]

Block是iOS4.0+ 和Mac OS X 10.6+ 引進的對C語言的擴展,用來實現匿名函數的特性。

用維基百科的話來說,Block是Apple Inc.為C、C++以及Objective-C添加的特性,使得這些語言可以用類lambda表達式的語法來創建閉包。

用Apple文檔的話來說,A block is an anonymous inline collection of code, and sometimes also called a "closure".

關于閉包,我覺得阮一峰的一句話解釋簡潔明了:閉包就是能夠讀取其它函數內部變量的函數。

這個解釋用到block來也很恰當:一個函數里定義了個block,這個block可以訪問該函數的內部變量。

一個簡單的Block示例如下:

  1. int (^maxBlock)(intint) = ^(int x, int y) { return x > y ? x : y; }; 

如果用Python的lambda表達式來寫,可以寫成如下形式:

  1. f = lambda x, y : x if x > y else y 

不過由于Python自身的語言特性,在def定義的函數體中,可以很自然地再用def語句定義內嵌函數,因為這些函數本質上都是對象。

如果用BNF來表示block的上下文無關文法,大致如下:

  1. block_expression  ::=  ^  block_declare  block_statement 
  2. block_declare  ::=  block_return_type  block_argument_list 
  3. block_return_type ::=  return_type  |  空 
  4. block_argument_list  ::=  argument_list  |  空

 


[1. Why block]

Block 除了能夠定義參數列表、返回類型外,還能夠獲取被定義時的詞法范圍內的狀態(比如局部變量),并且在一定條件下(比如使用__block變量)能夠修改這 些狀態。此外,這些可修改的狀態在相同詞法范圍內的多個block之間是共享的,即便出了該詞法范圍(比如棧展開,出了作用域),仍可以繼續共享或者修改 這些狀態。

通常來說,block都是一些簡短代碼片段的封裝,適用作工作單元,通常用來做并發任務、遍歷、以及回調。

比如我們可以在遍歷NSArray時做一些事情:

  1. - (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block; 

其中將stop設為YES,就跳出循環,不繼續遍歷了。

而在很多框架中,block越來越經常被用作回調函數,取代傳統的回調方式。

用block作為回調函數,可以使得程序員在寫代碼更順暢,不用中途跑到另一個地方寫一個回調函數,有時還要考慮這個回調函數放在哪里比較合適。采用block,可以在調用函數時直接寫后續處理代碼,將其作為參數傳遞過去,供其任務執行結束時回調。

另一個好處,就是采用block作為回調,可以直接訪問局部變量。比如我要在一批用戶中修改一個用戶的name,修改完成后通過回調更新對應用戶的單元格 UI。這時候我需要知道對應用戶單元格的index,如果采用傳統回調方式,要嘛需要將index帶過去,回調時再回傳過來;要嘛通過外部作用域記錄當前 操作單元格的index(這限制了一次只能修改一個用戶的name);要嘛遍歷找到對應用戶。而使用block,則可以直接訪問單元格的index。

這份文檔中提到block的幾種適用場合:

任務完成時回調
處理消息監聽回調處理
錯誤回調處理
枚舉回調
視圖動畫、變換
排序


[2. About __block_impl]

Clang提供了中間代碼展示的選項供我們進一步了解block的原理。

以一段很簡單的代碼為例:

使用-rewrite-objc選項編譯:

得到一份block0.cpp文件,在這份文件中可以看到如下代碼片段:

從命名可以看出這是block的實現,并且得知block在Clang編譯器前端得到實現,可以生成C中間代碼。很多語言都可以只實現編譯器前端,生成C中間代碼,然后利用現有的很多C編譯器后端。

從結構體的成員可以看出,Flags、Reserved可以先略過,isa指針表明了block可以是一個NSObject,而FuncPtr指針顯然是block對應的函數指針。

由此,揭開了block的神秘面紗。

不過,block相關的變量放哪里呢?上面提到block可以capture詞法范圍內(或者說是外層上下文、作用域)的狀態,即便是出了該范圍,仍然可以修改這些狀態。這是如何做到的呢?


[3. Implementation of a simple block]

先看一個只輸出一句話的block是怎么樣的。

生成中間代碼,得到片段如下:

首先出現的結構體就是__main_block_impl_0,可以看出是根據所在函數(main函數)以及出現序列(第0個)進行命名的。如果是全局 block,就根據變量名和出現序列進行命名。__main_block_impl_0中包含了兩個成員變量和一個構造函數,成員變量分別是 __block_impl結構體和描述信息Desc,之后在構造函數中初始化block的類型信息和函數指針等信息。

接著出現的是__main_block_func_0函數,即block對應的函數體。該函數接受一個__cself參數,即對應的block自身。

再下面是__main_block_desc_0結構體,其中比較有價值的信息是block大小。

***就是main函數中對block的創建和調用,可以看出執行block就是調用一個以block自身作為參數的函數,這個函數對應著block的執行體。

這里,block的類型用_NSConcreteStackBlock來表示,表明這個block位于棧中。同樣地,還有_NSConcreteMallocBlock和_NSConcreteGlobalBlock。

由 于block也是NSObject,我們可以對其進行retain操作。不過在將block作為回調函數傳遞給底層框架時,底層框架需要對其copy一 份。比方說,如果將回調block作為屬性,不能用retain,而要用copy。我們通常會將block寫在棧中,而需要回調時,往往回調block已 經不在棧中了,使用copy屬性可以將block放到堆中。或者使用Block_copy()和Block_release()。

別走開,下頁為您精彩繼續

#p#


[4. Capture local variable]

再看一個訪問局部變量的block是怎樣的。

生成中間代碼,得到片段如下:

可以看出這次的block結構體__main_block_impl_0多了個成員變量i,用來存儲使用到的局部變量i(值為1024);并且此時可以看到__cself參數的作用,類似C++中的this和Objective-C的self。

如果我們嘗試修改局部變量i,則會得到如下錯誤:

錯誤信息很詳細,既告訴我們變量不可賦值,也提醒我們要使用__block類型標識符。

為什么不能給變量i賦值呢?

因為main函數中的局部變量i和函數__main_block_func_0不在同一個作用域中,調用過程中只是進行了值傳遞。當然,在上面代碼中,我們 可以通過指針來實現局部變量的修改。不過這是由于在調用__main_block_func_0時,main函數棧還沒展開完成,變量i還在棧中。但是在 很多情況下,block是作為參數傳遞以供后續回調執行的。通常在這些情況下,block被執行時,定義時所在的函數棧已經被展開,局部變量已經不在棧中 了(block此時在哪里?),再用指針訪問就……。

所以,對于auto類型的局部變量,不允許block進行修改是合理的。


[5. Modify static local variable]

于是我們也可以推斷出,靜態局部變量是如何在block執行體中被修改的——通過指針。

因為靜態局部變量存在于數據段中,不存在棧展開后非法訪存的風險。

上面中間代碼片段與前一個片段的差別主要在于main函數里傳遞的是i的地址(&i),以及__main_block_impl_0結構體中成員i變成指針類型(int *)。

然后在執行block時,通過指針修改值。

當然,全局變量、靜態全局變量都可以在block執行體內被修改。更準確地講,block可以修改它被調用(這里是__main_block_func_0)時所處作用域內的變量。比如一個block作為成員變量時,它也可以訪問同一個對象里的其它成員變量。


[6. Implementation of __block variable]

那么,__block類型變量是如何支持修改的呢?

 

我們為int類型變量加上__block指示符,使得變量i可以在block函數體中被修改。

此時再看中間代碼,會多出很多信息。首先是__block變量對應的結構體:

由***個成員__isa指針也可以知道__Block_byref_i_0也可以是NSObject。

第二個成員__forwarding指向自己,為什么要指向自己?指向自己是沒有意義的,只能說有時候需要指向另一個__Block_byref_i_0結構。

***一個成員是目標存儲變量i。

此時,__main_block_impl_0結構如下:

__main_block_impl_0的成員變量i變成了__Block_byref_i_0 *類型。

對應的函數__main_block_func_0如下:

亮點是__Block_byref_i_0指針類型變量i,通過其成員變量__forwarding指針來操作另一個成員變量。 :-)

而main函數如下:

通過這樣看起來有點復雜的改變,我們可以修改變量i的值。但是問題同樣存在:__Block_byref_i_0類型變量i仍然處于棧上,當block被回調執行時,變量i所在的棧已經被展開,怎么辦?

在這種關鍵時刻,__main_block_desc_0站出來了:

此時,__main_block_desc_0多了兩個成員函數:copy和dispose,分別指向__main_block_copy_0和__main_block_dispose_0。

當block從棧上被copy到堆上時,會調用__main_block_copy_0將__block類型的成員變量i從棧上復制到堆上;而當block被釋放時,相應地會調用__main_block_dispose_0來釋放__block類型的成員變量i。

一會在棧上,一會在堆上,那如果棧上和堆上同時對該變量進行操作,怎么辦?

這時候,__forwarding的作用就體現出來了:當一個__block變量從棧上被復制到堆上時,棧上的那個__Block_byref_i_0結構體中的__forwarding指針也會指向堆上的結構。


本來還想繼續寫下去,結果發現文章有點長了。先到此。

責任編輯:閆佳明 來源: cocoachina
相關推薦

2017-02-15 09:25:36

iOS開發MQTT

2025-01-10 09:47:43

blockSDKiOS

2010-09-16 09:13:09

CSS display

2017-03-07 09:45:43

iOSBlock開發

2013-07-19 12:52:50

iOS中BlockiOS開發學習

2015-12-23 09:16:33

ios動畫渲染機制

2015-12-30 14:16:05

iOS動畫視圖渲染

2018-05-27 17:44:53

私有庫索引庫倉庫

2011-08-08 18:11:45

IOS 4Block UIActionShe

2010-09-28 15:38:23

Java ME

2013-07-19 14:00:13

iOS中BlockiOS開發學習

2013-07-19 14:35:59

iOS中BlockiOS開發學習

2016-03-07 09:09:35

blockios開發實踐

2024-08-28 08:00:00

2013-07-19 13:16:26

iOS中BlockiOS開發學習內存管理

2010-08-02 16:51:54

2017-03-06 16:13:41

深度學習人工智能

2015-03-18 09:29:12

iOS開發爭議

2010-09-29 09:54:09

J2ME應用程序

2010-09-30 13:06:33

Myeclipse J
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩毛片免费看 | 91综合网 | 天天躁日日躁aaaa视频 | 婷婷丁香在线视频 | 免费在线观看av | 午夜激情一区 | 一级片av| 免费毛片www com cn | 天天色天天| 亚洲三级av | 免费的黄色片子 | 色在线看 | 欧美成人h版在线观看 | 精品一区二区av | 国产美女黄色片 | 羞视频在线观看 | 色毛片 | 国产色爽| 性一交一乱一透一a级 | 精品欧美一区二区三区久久久 | 国产免费国产 | 一区二区视频在线 | 亚洲欧美日韩国产综合 | 亚洲二区视频 | 2018国产精品 | 国产在线观看网站 | 国产 日韩 欧美 在线 | 精品欧美乱码久久久久久1区2区 | 亚洲欧美日韩精品久久亚洲区 | 成人国产精品久久久 | 超碰婷婷| 国产一级在线 | 蜜月aⅴ免费一区二区三区 99re在线视频 | 亚洲成在线观看 | 国产福利久久 | 欧美日韩国产中文 | 成人免费视频网站 | 一区二区不卡视频 | 激情视频一区 | 国产精品久久视频 | 久久成人免费 |