【ios開發系列】block詳解
block到底是什麼
我們使用clang的rewrite-objc命令來獲取轉碼後的程式碼。
1、block的底層實現
我們來看看最簡單的一個block:
這個block僅僅列印棧變數i和j的值,其被clang轉碼為:
首先是一個結構體__main_block_impl_0(從圖二中的最後一行可以看到,block是一個指向__main_block_impl_0的指標,初始化後被型別強轉為函式指標),其中包含的__block_impl是一個公共實現(學過c語言的同學都知道,__main_block_impl_0的這種寫法表示其可以被型別強轉為__block_impl型別):
- struct __block_impl {
- void *isa;
- int Flags;
- int Reserved;
- void *FuncPtr;
- };
isa指標說明block可以成為一個objc物件。
__main_block_impl_0的意思是main函式中的第0個block的implementation,這就是這個block的主體了。
這個結構體的建構函式的引數:
block實際執行程式碼所在的函式的指標,當block真正被執行時,實際上是呼叫了這個函式,其命名也是類似的方式。
block的描述結構體,注意這個結構體宣告結束時就建立了一個唯一的desc,這個desc包含了block的大小,以及複製和析構block時需要額外呼叫的函式。
接下來是block所引用到的變數們
最後是一個標記值,內部實現需要用到的。(我用計算器看了一下,570425344這個值等於1<<29,即BLOCK_HAS_DESCRIPTOR這個列舉值)
所以,我們可以看到:
為什麼上一篇我們說j已經不是原來的j了,因為j是作為引數傳入了block的建構函式,進行了值複製。
帶有__block標記的變數會被取地址來傳入建構函式,為修改其值奠定了基礎
接下來是block執行函式__main_block_func_0:
其唯一的引數是__main_block_impl_0的指標,我們看到printf語句的資料來源都取自__cself這個指標,比較有意思的是i的取值方式(帶有__block標記的變數i被轉碼為一個結構體),先取__forward指標,再取i,這為將i複製到堆中奠定了基礎。
再下來是預定義好的兩個複製/釋放輔助函式,其作用後面會講到。
最後是block的描述資訊結構體 __main_block_desc_0,其包含block的記憶體佔用長度,已經複製/釋放輔助函式的指標,其宣告結束時,就建立了一個名為__main_block_desc_0_DATA的結構體,我們看它構造時傳入的值,這個DATA結構體的作用就一目瞭然了:
長度用sizeof計算,輔助函式的指標分別為上面預定義的兩個輔助函式。
注意,如果這個block沒有使用到需要在block複製時進行copy/retian的變數,那麼desc中不會有輔助函式
至此,一個block所有的部件我們都看齊全了,一個主體,一個真正的執行程式碼函式,一個描述資訊(可能包含兩個輔助函式)。
2、構造一個block
我們進入main函式:
圖一中的第三行(block的宣告),在圖二中,轉化為一個函式指標的宣告,並且都沒有被賦予初始值。
而圖一中的最後一行(建立一個block),在圖二中,成為了對__main_block_impl_0的建構函式的呼叫,傳入的引數的意義上面我們已經講過了。
所以構造一個block就是建立了__main_block_impl_0 這個c++類的例項。
3、呼叫一個block
呼叫一個block的寫法很簡單,與呼叫c語言函式的語法一樣:
- blk();
其轉碼後的語句:
- ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
將blk這個函式指標型別強轉為__block_impl型別,然後取其執行函式指標,然後將此指標型別強轉為返回void*並接收一個__block_impl*的函式指標,最後呼叫這個函式,傳入強轉為__block_impl*型別的blk,
即呼叫了前述的函式__main_block_func_0
4、objective-c類成員函式中的block
原始碼如下:
- - (void)of1
- {
- OBJ1* oj = self;
- void (^oblk)(void) = ^{ printf("%d\n", oj.oi);};
- Block_copy(oblk);
- }
這裡我故意將self賦值給oj這個變數,是為了驗證前一章提出的一個結論:無法通過簡單的間接引用self來防止retain迴圈,要避免迴圈,我們需要__block標記(多謝樓下網友的提醒)
轉碼如下:
- struct __OBJ1__of1_block_impl_0 {
- struct __block_impl impl;
- struct __OBJ1__of1_block_desc_0* Desc;
- OBJ1 *oj;
- __OBJ1__of1_block_impl_0(void *fp, struct __OBJ1__of1_block_desc_0 *desc, OBJ1 *_oj, int flags=0) : oj(_oj) {
- impl.isa = &_NSConcreteStackBlock;
- impl.Flags = flags;
- impl.FuncPtr = fp;
- Desc = desc;
- }
- };
- staticvoid __OBJ1__of1_block_func_0(struct __OBJ1__of1_block_impl_0 *__cself) {
- OBJ1 *oj = __cself->oj; // bound by copy
- printf("%d\n", ((int (*)(id, SEL))(void *)objc_msgSend)((id)oj, sel_registerName("oi")));}
objc方法中的block與c中的block並無太多差別,只是一些標記值可能不同,為了標記其是objc方法中的blcok。
注意其建構函式的引數:OBJ1 *_oj
這個_oj在block複製到heap時,會被retain,而_oj與self根本就是相等的,所以,最終retain的就是self,所以如果當前例項持有了這個block,retain迴圈就形成了。
而一旦為其增加了__block標記:
- - (void)of1
- {
- __block OBJ1 *bSelf = self;
- ^{ printf("%d", bSelf.oi); };
- }其轉碼則變為:
- //增加了如下行
- struct __Block_byref_bSelf_0 {
- void *__isa;
- __Block_byref_bSelf_0 *__forwarding;
- int __flags;
- int __size;
- void (*__Block_byref_id_object_copy)(void*, void*);
- void (*__Block_byref_id_object_dispose)(void*);
- OBJ1 *bSelf;
- };
- staticvoid __Block_byref_id_object_copy_131(void *dst, void *src) {
- _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
- }
- staticvoid __Block_byref_id_object_dispose_131(void *src) {
- _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
- }
- //宣告處變為
- __block __Block_byref_bSelf_0 bSelf = {(void*)0,(__Block_byref_bSelf_0 *)&bSelf, 33554432, sizeof(__Block_byref_bSelf_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self};
clang為我們的bSelf結構體建立了自己的copy/dispose輔助函式,33554432(即1<<25 BLOCK_HAS_COPY_DISPOSE)這個值告訴系統,我們的bSelf結構體具有copy/dispose輔助函式。
而131這個引數(二進位制1000 0011,即BLOCK_FIELD_IS_OBJECT (3) |BLOCK_BYREF_CALLER(128))
中的BLOCK_BYREF_CALLER在內部實現中告訴系統不要進行retain或者copy,
也就是說,在 __block bSelf 被複制至heap上時,系統會發現有輔助函式,而輔助函式呼叫後,並不retain或者copy 其結構體內的bSelf。
這樣就避免了迴圈retain。
小結:
當我們建立一個block,並呼叫之,編譯器為我們做的事情如下:
1.建立block所有的部件程式碼:一個主體,一個真正的執行程式碼函式,一個描述資訊(可能包含兩個輔助函式)。
2.將我們的建立程式碼轉碼為block_impl的構造語句。
3.將我們的執行語句轉碼為對block的執行函式的呼叫。
下一篇我們將剖析runtime.c的原始碼,並理解block的堆疊轉換。
【4】
終於有空開始這系列最後一篇的編寫。這一篇,我們將看到block的記憶體管理的內部實現,通過剖析runtime庫原始碼,我們可以更深刻的理解block的記憶體運作體系。
看此篇時,請大家同時開啟兩個網址(或者下載它們到本地然後開啟):
記憶體管理的真面目
objc層面如何區分不同記憶體區的block
Block_private.h中有這樣一組值:
- /* the raw data space for runtime classes for blocks */
- /* class+meta used for stack, malloc, and collectable based blocks */
- BLOCK_EXPORT void * _NSConcreteStackBlock[32];
- BLOCK_EXPORT void * _NSConcreteMallocBlock[32];
- BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
- BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
- BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
- BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
其用於對block的isa指標賦值
1.棧
- struct __OBJ1__of2_block_impl_0 {
- struct __block_impl impl;
- struct __OBJ1__of2_block_desc_0* Desc;
- OBJ1 *self;
- __OBJ1__of2_block_impl_0(void *fp, struct __OBJ1__of2_block_desc_0 *desc, OBJ1 *_self, int flags=0) : self(_self) {
- impl.isa = &_NSConcreteStackBlock;
- impl.Flags = flags;
- impl.FuncPtr = fp;
- Desc = desc;
- }
- };
在棧上建立的block,其isa指標是_NSConcreteStackBlock。
2.全域性區
在全域性區建立的block,其比較類似,其建構函式會將isa指標賦值為_NSConcreteGlobalBlock。
3.堆
我們無法直接建立堆上的block,堆上的block需要從stack block拷貝得來,在runtime.c中的_Block_copy_internal函式中,有這樣幾行:
- // Its a stack block. Make a copy.
- if (!isGC) {
- struct Block_layout *result = malloc(aBlock->descriptor->size);
- ...
- result->isa = _NSConcreteMallocBlock;
- ...
- return result;
- }
可以看到,棧block複製得來的新block,其isa指標會被賦值為_NSConcreteMallocBlock
4.其餘的isa型別
- BLOCK_EXPORT
相關推薦
【ios開發系列】block詳解
block到底是什麼 我們使用clang的rewrite-objc命令來獲取轉碼後的程式碼。 1、block的底層實現 我們來看看最簡單的一個block: 這個block僅僅列印棧變數i和j的值,其被clang轉碼為: 首先是一個結構體__main
【iOS開發系列】九宮格布局
使用 objc with div self. orm i++ back hab /** * 這個盡管非常easy,算是一個小技巧,可是碰到了就記錄下來吧.積跬步,致千裏嘛. */ - (void)scratchableLatex { for (int i=
【iOS開發系列】NSObject方法介紹
ati ber oid ring cto rgb dst -s 推斷 NSObject是OC中的基類,全部類都繼承於此,這裏面也給我們提供了非常多與“類”和“方法”相關的方法,本文將解說幾個非常有用的方法。 正文: Per
【iOS開發系列】將阿拉伯數字轉換為中文數字
/** * 將阿拉伯數字轉換為中文數字 */ +(NSString *)translationArabicNum:(NSInteger)arabicNum { NSString *arabicNumStr = [NSString stringWithForma
3天搞定的小型B/S內部管理類軟體定製開發專案【軟體開發實戰10步驟詳解】
十一休假,杭州西湖邊逛了一圈只能用人山人海來形容,浙大紫金港校區也逛了一圈風景如畫,建設得真不錯很棒,假期就去了這2個地方,然後在家裡陪老婆、看孩子、洗尿布、打了幾局星際爭霸,在網上接了一個B/S架構的內部管理類定製軟體、淘寶上收了600元辛苦費後就開始行動了、現在把整個開發過程講解分享如下文
【iOS開發-79】利用Modal方式實現控制器之間的跳轉
article 運用 mis cli 控制 present 沒有 dismiss 導航控制器 利用Modal方法。事實上就是以下兩個方法的運用。Modal方式的切換效果是從底部呈現。 -(void)clickModal{ WPViewController *wp
【iOS開發-51】案例學習:動畫新寫法、刪除子視圖、視圖順序、延遲方法、button多功能使用方法及icon圖標和啟動頁設置
無法查看 font targe 技術 value lstat tostring sta dict 案例效果: (1)導入所需的素材,然後用storyboard把上半截位置和大小相對固定的東西布局起來。當然,這些控件也要定義成對應地IBOutlet和IBActio
【iOS開發-32】iOS程序真機調試須要購買調試證書怎麽辦?
pos 上進 ack tracking popu 怎麽辦 ont 調試 開發程序 一、情況 我們在開發iOS程序的時候,一般都是在模擬器上執行查看效果的。可是,當開完完畢。須要在真機上調試怎麽辦? 二、官方解決的方法 蘋果有為個人和企業開發人員提供調試證書和
【iOS開發-47】怎樣下載iOS 7.1 Simulator 以及iOS 8離線的Documentation這些文件?
hang http 下載 資料 zhang 目錄 ios 8 log targe (1)最官方的解決的方法 在Xcode6裏面提供下載。依照下圖找到下載就可以。一般建議把以下的自己主動檢查更新和下載的框框勾起來,這樣它會幫我們自己主動下載。 問題
【黑馬PHP教程】錯誤詳解
error_log 報告 color 顯示 自定義 borde 技術 誤報 處理 一,錯誤通常分3種 二,錯誤的分級 三,錯誤的觸發 四,顯示錯誤報告 問題一:設置顯示錯誤報告 問題二:顯示哪些級別的錯誤報告 五,錯誤日誌的記錄問題 問題一
iOS開發多執行緒詳解
在iOS開發中,多執行緒開發是非常重要的核心之一,這篇文章和大家分享一下多執行緒的進階-死鎖. iOS有三種多執行緒程式設計的技術,分別是:(一)NSThread(二)Cocoa NSOperation(三)GCD(全稱:Grand Central Dispatch) 如果你對多執行緒
iOS開發之AddressBookUI框架詳解
iOS開發之AddressBookUI框架詳解 一、關於AddressBookUI框架 AddressbookUI是iOS開發框架中提供的一套通訊錄介面元件。其中封裝好了一套選擇聯絡人,檢視聯絡人的介面,在需要時開發者可以直接呼叫。當然對於聯絡人介面,
iOS開發之AddressBook框架詳解
iOS開發之AddressBook框架詳解 一、寫在前面 首先,AddressBook框架是一個已經過時的框架,iOS9之後官方提供了Contacts框架來進行使用者通訊錄相關操作。儘管如此,AddressBook框架依然是一個非常優雅並且使用方便的通
iOS開發之Accounts框架詳解
iOS開發之Accounts框架詳解 Accounts框架是iOS原生提供的一套賬戶管理框架,其支援Facebook,新浪微博,騰訊微博,Twitter和領英賬戶管理的功能。需要注意,在iOS 11及以上系統中,將此功能已經刪除,因此Accounts.frame
【java專案實戰】Servlet詳解以及Servlet編寫登陸頁面(二)
Servlet是Sun公司提供的一門用於開發動態web網頁的技術。Sun公司在API中提供了一個servlet介面,我們如果想使用java程式開發一個動態的web網頁,只需要實現servelet介面,並把類部署到web伺服器上就可以運行了。 到底什麼是Ser
【iOS開發-29】解決方案:TabBar的圖片不顯示,只顯示灰色的正方形
(1)現象 tabbar上的圖片變成一塊正方形的灰色塊塊,原先的圖片沒有了。 (2)原因 tabbar上的圖片本質上不是一個圖片,而是一個形狀圖片。系統對我們使用的圖片也只是把其中的形狀“扣”出來,其餘的背景什麼的都不要。因為我們可能給背景加了顏色,所以系統扣的時候只是把
【kubernetes/k8s概念】CNI詳解
1、為什麼CNI CNI是Container Network Interface的是一個標準的,通用的介面。現在容器平臺:docker,kubernetes,mesos,容器網路解決方案:flannel,calico,weave。只要提供一個標準的介面,就能為
【IOS開發基礎】之判斷NSString為純數字
//判斷是否為整形: - (BOOL)isPureInt:(NSString*)string{ NSScanner* scan = [NSScanner scannerWithString:string]; int val; return
iOS開發-底層篇-Class詳解,ios底層-class詳解
前言:iOS的開發語言objective-c,它的真實面目是它不是真正的面嚮物件語言,而抽象理解為此而已。其實它就是C+,有個公式可以很好地詮釋那就是 OC = C + Runtime; 接下來我們就好好講講在Runtime下的objc-class。準備資料,objc
【多執行緒】BlockingQueue詳解
前言: 在新增的Concurrent包中,BlockingQueue很好的解決了多執行緒中,如何高效安全“傳輸”資料的問題。通過這些高效並且執行緒安全的佇列類,為我們快速搭建高質量的多執行緒程式帶來極大的便利。本文詳細介紹了BlockingQueue家庭中的所有成員