block本質探尋二之變數捕獲

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

一、代碼 說明:本文章須結合文章《block本質探尋一之記憶體結構》和《class和object_getClass方法區別》加以理解; //main.m //列印 分析:很顯然,只有c的值沒有改變,其它變數的值都改變了——為什麼,看下底層代碼實現; 二、main.cpp 分析: 1)C語言語法 <1> ...


一、代碼

說明:本文章須結合文章《block本質探尋一之記憶體結構》和《class和object_getClass方法區別》加以理解;

//main.m

#import <Foundation/Foundation.h>

int a = 10;
static int b = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        auto int c = 20;
        static int d = 20;
        
        void (^block)(void) = ^{
            NSLog(@"a=%d, b=%d, c=%d, d=%d", a, b, c, d);
        };
        
        a = 30;
        b = 35;
        c = 40;
        d = 45;
        
        block();
    }
    return 0;
}

//列印

2019-01-09 15:42:16.246684+0800 MJ_TEST[4180:224738] a=30, b=35, c=20, d=45
Program ended with exit code: 0

分析:很顯然,只有c的值沒有改變,其它變數的值都改變了——為什麼,看下底層代碼實現;

 

二、main.cpp

int a = 10;
static int b = 10;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int c;
  int *d;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int *_d, int flags=0) : c(_c), d(_d) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int c = __cself->c; // bound by copy
  int *d = __cself->d; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_1f1f41_mi_0, a, b, c, (*d));
        }

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)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        auto int c = 20;
        static int d = 20;

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, &d));

        a = 30;
        b = 35;
        c = 40;
        d = 45;

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

分析:

1)C語言語法

<1>int c被轉換成auto int c:我們知道c、d為局部變數,而a、b為全局變數,C語言中,所有沒有修飾符的局部變數預設的修飾符為auto,static修飾的變數為靜態變數,還有一個register註冊類的在此不再贅述(自己有興趣上網查下);

<2>auto類型的局部變數的生命周期為離其最近的大括弧內,超出該大括弧,該變數被自動銷毀;

<3>static類型的變數(不論是全局還是局部),其值一直保留在記憶體中,不受大括弧的限制,程式結束時才被銷毀;

2)變數捕獲概念

我們發現在block結構體中,存在c、d而不存在a、b變數,在此,我們把存在於block結構體中的外部變數稱為變數捕獲,不存在的則沒有被捕獲,所以a、b變數沒有被block捕獲;

3)變數調用流程

<1>在block結構體中,c保持不變依然為int型變數,而d被轉換成int型指針變數,因此在main函數中通過__main_block_impl_0方法傳遞實參c本身的值和d指向的記憶體的值&d;

而在block的構造函數中,c(_c), d(_d)為C++語法<=>c = _c,d = _d,那麼main函數中的實參c、&d最終傳遞給了block結構體中的變數c和指針變數d;

<2>最後在__main_block_func_0方法中,對c、d而言,須先獲取block內部的成員變數再輸出;而對於a、b,因為是全局變數,所以可以直接引用;

 

綜上所述:

auto局部變數因為作用域(或生命周期)有限,隨時會銷毀,故block在引用時系統會自動將其值保存在block結構體中(即捕獲);而全局變數和static修飾的變數(局部或全局),並不會隨時被銷毀,其值一直會在記憶體中保持不變,知道整個程式結束時才銷毀

1)另外從另一個角度理解,全局變數其作用域為從其定義的地方開始到該文件結束止都是有效的,所以main函數中可以用,__main_block_func_0函數中也可以用,不需要再將其保存到block自身的結構體中;

2)static修飾的局部變數會被轉化成指針變數,而保存到block結構體中也是指針,因為指針本身的值為另一個變數的地址,所以block對該指針的操作始終是對另一個變數的地址的操作,而非另一個變數值的本身,當對d重新賦值時,block中的指針變數指向的變數的值也就隨之改變,對*d輸出當然被改變(*d即取出指向的記憶體地址存放的值);

3)auto局部變數被捕獲,即是在記憶體中重新開闢了記憶體來存放該變數的值(即copy),只不過是在block結構體對應的記憶體中;

 

三、結論

1.

2.auto修飾的局部變數在block定義後的修改,不影響block內部對該變數的使用;後兩者,有影響;

 

四、擴展——OC對象捕獲問題

1)Person.m——註:此處我將.h文件也貼過來了,為了很好的閱讀

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

- (instancetype)initWithName:(NSString *)name;

@end

NS_ASSUME_NONNULL_END




#import "Person.h"

int weight_ = 10;

@implementation Person

- (void)test
{
    void(^block)(void) = ^{
        NSLog(@"-----%p", self);
        NSLog(@"-----%@", _name);
        NSLog(@"-----%@", self.name);
        NSLog(@"-----%d", weight_);
    };
}

- (instancetype)initWithName:(NSString *)name
{
    self = [super init];
    if (self) {
        
    }
    return self;
}

@end

 

2)Person.cpp——註:此處只對.m文件進行轉化

問題一:參數

static void _I_Person_test(Person * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
}


static instancetype _Nonnull _I_Person_initWithName_(Person * self, SEL _cmd, NSString * _Nonnull name) {
    self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"));
    if (self) {

    }
    return self;
}

分析:

<1>在.m文件內部有兩個方法test和initWithName,前者不帶參數,後者帶一個參數name,但是轉成C++後,發現,兩個方法前面均自動加上了兩個參數:Person * self, SEL _cmd;這是每個方法必備的兩個參數,前者是調用對象本身self,後者是方法名;

<2>此處的self為實例對象而非類對象(驗證方法:在test方法中列印self的地址%p,會發現每次調用的值都不一樣,而類對象在記憶體中只有一份;

 

問題二:self捕獲

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  Person *self;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

分析:

<1>self被捕獲到block結構體體中,那麼可以肯定self是auto類型的局部變數;

<2>從另一個角度理解:self作為實參從main函數中傳遞到block結構體的構造函數__Person__test_block_impl_0的形參_self,再將_slef賦值於self;而參數本身就是一個auto類型的局部變數,函數結束後就自動被銷毀;

 

問題三:block代碼塊執行

int weight_ = 10;

static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_0, self);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_1, (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_name)));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_2, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_3, weight_);
    }

分析:

<1>self作為auto類型的局部變數,輸出前需先從block結構體中取出該成員變數;

<2>weight作為全局變數,直接引用,無須捕獲;

<3>以_name來引用對象屬性,其本質是block的成員變數,存放在類對象的結構體記憶體中,而結構體指針變數須通過"->"來引用該結構體成員變數,但是self作為實例對象與Person類對象不是同一個,問什麼能通過"->"來引用?

————原因:self的第一個成員變數為isa,而isa是指向類對象的指針,即類對象的首地址跟實例對象的首地址是同一個地址,而結構體成員變數在記憶體中的地址是連續的,因此self可以通過"->"形式來找到_name成員變數;

<4>self.name即通過getter方法來訪問name值,轉換成C++為objc_msgSend方法,即通過消息轉發機制來訪問name值,而消息轉發機制的本質是通過isa來找到類對象,進而訪問該類對象中的name成員變數;

 

3)結論

【1】oc實例對象self會被捕獲到block結構體中;

【2】@property聲明的屬性的引用,須先執行【1】步驟,因此嚴格上講也是被捕獲到block中;

【3】.m文件中聲明的全局變數,不受self影響,依然不會被捕獲到block中,;

 

 

GitHub


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

-Advertisement-
Play Games
更多相關文章
  • 導出完成後在狀態欄中顯示Find ...
  • 節點一 alert日誌: PDB(17):Transaction recovery: lock conflict caught and ignored PDB(17):Transaction recovery: lock conflict caught and ignored PDB(17):Tra ...
  • 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;; 我們來看看它的執行計劃 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...