block本質探尋一之記憶體結構

来源:https://www.cnblogs.com/lybSkill/archive/2019/01/09/10240705.html
-Advertisement-
Play Games

一、代碼——命令行模式 //main.m block(20, 30); 分析:以下代碼的前提,因為我們知道block底層的構造就是上述結構體的構造,橋接的目的就是展示這樣的結構體內部是怎樣的; 二、調試 //lldb模式 1)第一個斷點 2)第二個斷點 3)轉入彙編 4)彙編界面 分析: 1)我們發 ...


一、代碼——命令行模式

//main.m

#import <Foundation/Foundation.h>

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int age;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
//        ^{
//            NSLog(@"This is a block");
//        }();
        
        int age = 10;
        void (^block)(int, int) = ^(int a, int b){
            NSLog(@"This is a block:%d", age);
            NSLog(@"a:%d b:%d", a, b);
        };
        
        struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;
        

         block(20, 30);

    }
    return 0;
}

分析:以下代碼的前提,因為我們知道block底層的構造就是上述結構體的構造,橋接的目的就是展示這樣的結構體內部是怎樣的;

struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;

二、調試

//lldb模式

1)第一個斷點

 

 2)第二個斷點

 

3)轉入彙編

 

 

4)彙編界面

 

 

分析:

1)我們發現內部變數的層次感:

第一層:包含impl、Desc、age;

第二層:impl包含isa、Flags、Reserved、FuncPtr;

2)block大括弧內部的代碼的第一行的地址跟FuncPtr指針指向的地址是一樣的,那麼block大括弧內的代碼是如何存放的,跟FuncPtr指針有什麼關係?往下看;

 

 三、將main.m文件轉成底層實現代碼(即C++代碼)

1)命令行:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

 

說明:警告不用管; 

 

2)找到main.cpp文件

 

3)拖入文件並打開

說明:不對mian.cpp文件編譯的目的是,自己可以任意對該文件的代碼操作而不報錯; 

 

  

 

 說明:

1)上面兩張圖中,1、2、3是一一對應關係(1:為block要引用的外部變數;2:定義的block;3:調用block);

2)在2處,本人把一些強制轉換去除了(如:void (*)等),便於閱讀;

四、底層代碼分析

1)block結構體(對2的分析)

很明顯,block是一個指針,指向__main_block_impl_0,那__main_block_impl_0又是什麼呢,往下看;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__main_block_impl_0是個結構體,內部成員變數有__block_impl類型的結構體變數,__main_block_desc_0類型的結構體變數,外部應用變數,以及一個__main_block_impl_0方法(該方法名跟所在的結構體名稱相同,為C++的一個構造方法,類似於init方法);

<1>__block_impl

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

該結構體有四個成員變數,這跟上述lldb模式下顯示的成員變數相同,而第一個成員變數為isa指針,我們知道這是oc對象(實例、類、元類)的專屬標誌,很顯然__main_block_impl_0是一個oc對象,而oc對象的本質就是在記憶體中為結構體,此處完全吻合;

<2>__main_block_desc_0

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

該結構體有兩個成員變數,同時定義了一個結構體變數並對其賦值,reserved賦值為0,Block_size賦值為__main_block_impl_0即block的記憶體大小;

<3>__main_block_impl_0構造函數

__main_block_impl_0在main 函數中傳了三個參數:

//__main_block_func_0參數

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_51dde3_mi_0, age);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_51dde3_mi_1, a, b);
        }

很顯然,__main_block_func_0函數存放的是block大括弧裡面的代碼,而該函數是直接賦值給__main_block_impl_0構造函數的第一個參數fp指針,而fp又賦值給__block_impl結構體中的FuncPtr變數,因此,回顧上述lldb模式,FuncPtr指針存放的地址跟37號斷點處轉入彙編模式顯示的首地址是一樣的,結合此處,可以肯定,FuncPtr變數指向block大括弧內的代碼,該代碼存放在記憶體中的分配好的函數中(__main_block_func_0);

//__main_block_desc_0_DATA參數

__main_block_desc_0_DATA是一個結構體,包含了block的記憶體大小,最終賦值給__main_block_desc_0結構體類型變數Desc;

//age直接賦值給_age

構造函數中的第四個參數flags預設設置為0;

 

2)block調用(對3的分析)

block調用代碼可以簡化成以下代碼

(__block_impl *)block->FuncPtr(block, 20, 30);

<1>參數:經上述分析,我們知道FuncPtr指向block大括弧內的代碼塊即__main_block_func_0函數,該函數共有三個參數,block本身,和兩個int類型變數,實參與形參一一對應,這點沒問題;

<2>強引用:因為block是一個結構體指針,其引用結構體變數只能通過"->"形式引用(如果是結構體變數非指針,則可以通過點(.)引用——此處是C語言語法知識,稍啰嗦了點!);

但是FuncPtr並不是block(即__main_block_impl_0)結構體的成員變數,為什麼能直接引用,而不應該是block->impl.FuncPtr嗎?

我們看到block進行了強制轉換(__block_impl *),而__block_impl結構體中是存在FuncPtr變數的,但這完全是兩個不同的結構體,也不能強制轉換引用啊?

我們可以看到,__block_impl結構體在__main_block_impl_0結構體中是第一個成員變數類型,即__main_block_impl_0的首地址其實就是__block_impl結構體的首地址,也就是說,__main_block_impl_0結構體的地址是從isa指針變數開始的,即__main_block_impl_0結構體在__block_impl結構體的大小範圍內跟__block_impl結構體是完全重合的(其實就是同一片記憶體),只不過__main_block_impl_0結構體大小要比__block_impl結構體大——因此,經過強制轉換後block完全可以直接引用FuncPtr成員變數;

 

五、結論

【1】block本質是一個oc對象,以結構體形式存放在記憶體中;block本身是一個指針,存放的是該結構體的記憶體地址(可以把block理解成函數名——函數名也是指針,指向函數的入口地址);

【2】block大括弧內的代碼存放在固定的函數中,該函數的入口地址存放在block結構體的成員變數指針(FuncPtr)中;

【3】block結構體分為函數調用結構體變數impl(包含isa指針變數、函數調用指針變數)、信息描述結構體指針變數Desc(包含block記憶體大小成員變數)、外部引用變數(age),以及構造函數;

如下圖所示(構造函數沒有寫出來,size即為block記憶體大小):

 

 

 

GitHub

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • mysql從5.0版本開始支持存儲過程、存儲函數、觸發器和事件功能的實現。 我們以一本書中的例題為例:創建xscj資料庫的存儲過程,判斷兩個輸入的參數哪個更大。並調用該存儲過程。 (1)調用 首先,創建存儲過程(procedure),名為xscj.compar 執行結果如下: 在上邊的語句中: cr ...
  • 報錯原因: 報這個錯是因為MySQL8使用了 caching_sha2_password 加密方式而之前MySQL使用的是 mysql_native_password 加密方式,而你的Navicat不支持 caching_sha2_password 加密方式造成的。 解決方案: 目前我知道的解決方案 ...
  • 前言幾個故事大數據都是騙人的,一頭豬悲催的豬數據不全不是大數據,不可信過去->將來啤酒尿布這個案例僅是一碗數據分析的“心靈雞湯”——聽起來很爽,但信不得!GFT 預測 H1N1搜索詞和H1N1敏感性關聯“預測即干涉”悖論和“菜農種菜”,過度擬合數據並非越大越好:谷歌流感趨勢錯在哪兒了?更準確的預測模... ...
  • 我們要做到不但會寫SQL,還要做到寫出性能優良的SQL語句。 1.使用表的別名(Alias): 當在SQL語句中連接多個表時, 請使用表的別名並把別名首碼於每個Column上。這樣一來,就可以減少解析的時間並減少那些由Column歧義引起的語法錯誤。 2.表的索引: 索引是表的一個概念部分,用來提高 ...
  • 一、 軟體下載 Mysql是一個比較流行且很好用的一款資料庫軟體,如下記錄了我學習總結的mysql免安裝版的配置經驗,要安裝的朋友可以當做參考哦 mysql5.7 64位下載地址: https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.19- ...
  • 1. 如何對評論進行分頁展示 一般情況下都是這樣寫 SELECT customer_id,title,content FROM product_comment WHERE audit_status = 1 AND product_id =199726 LIMIT 0,15;; 我們來看看它的執行計劃 ...
  • 一、代碼 說明:本文章須結合文章《block本質探尋一之記憶體結構》和《class和object_getClass方法區別》加以理解; //main.m //列印 分析:很顯然,只有c的值沒有改變,其它變數的值都改變了——為什麼,看下底層代碼實現; 二、main.cpp 分析: 1)C語言語法 <1> ...
  • public class GZIP { /** * 字元串的壓縮 * * @param str * 待壓縮的字元串 * @return 返回壓縮後的字元串 * @throws IOException */ public static String compress(String str)... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...