block本質探尋六之修改變數

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

說明: <1>閱讀本文章,請參照前面的block文章加以理解; <2>本文的變數指的是auto類型的局部變數(包括實例對象); <3>ARC和MRC兩種模式均適用; 一、無法修改的原因 //代碼 很明顯,強行給age賦值會報錯; //列印 分析:為什麼在block內部不能改變age的值?往下看 // ...


說明:

<1>閱讀本文章,請參照前面的block文章加以理解;

<2>本文的變數指的是auto類型的局部變數(包括實例對象);

<3>ARC和MRC兩種模式均適用;

一、無法修改的原因

//代碼

 

很明顯,強行給age賦值會報錯;

void test1()
{
    int age = 10;
    block = ^{
//        age = 20;
        NSLog(@"%d", age);
    };
}

//列印

2019-01-15 15:00:43.641417+0800 MJ_TEST[3676:199449] 10
Program ended with exit code: 0

分析:為什麼在block內部不能改變age的值?往下看

//clang

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


static void __test1_block_func_0(struct __test1_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_d8e7e4_mi_0, age);
    }


void test1()
{
    int age = 10;
    block = ((void (*)())&__test1_block_impl_0((void *)__test1_block_func_0, &__test1_block_desc_0_DATA, age));
}

分析:

<1>age被捕捉到block結構體中,根據輸出的結果很明顯是在ARC模式下,因此當被強指針變數block持有時,系統會自動將block對象從棧區拷貝到堆區;而MRC模式下,因為block對象會隨著test1()方法結束,其記憶體地址會被回收,age的值為亂碼

2019-01-15 16:12:43.234301+0800 MJ_TEST[4434:243013] -272632456
Program ended with exit code: 0

<2>block代碼塊是通過__test1_block_func_0函數來實現,而該函數應用的age就是block對象結構體__test1_block_impl_0中的age,這跟test1()方法中的age是兩個不同的age:前者存在於堆區,後者存在於棧區;

<3>想要在__test1_block_impl_0函數中去改變test1()方法中的局部變數,顯然是不成立的,根本就拿不到該局部變數;

 結論:block對象本身的代碼塊是存放在一個新的函數中,而block引用的外部auto類型的局部變數存在於block指針變數所在的函數中——所以,兩個不同的函數間彼此不可能改變對方內部的局部變數;

 

二、修改方法

1)static修飾

//代碼

void test2()
{
    static int age = 10;
    block = ^{
        age = 20;
        NSLog(@"%d", age);
    };
}

//列印

2019-01-15 16:31:56.533685+0800 MJ_TEST[4676:255859] 20
Program ended with exit code: 0

//clang

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



static void __test2_block_func_0(struct __test2_block_impl_0 *__cself) {
  int *age = __cself->age; // bound by copy

        (*age) = 20;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_bf7285_mi_1, (*age));
    }

分析:

<1>根據前述文章,此時test2()方法中的整型age是以指針的形式被捕捉到block對象結構體中,該指針變數指向值為10的記憶體區域;

<2>通過指針當然可以變數該指針指向的記憶體區域的值(見“__test2_block_func_0”函數),這點沒問題——C語言語法基礎;

結論:通過static修飾auto類型的局部變數來改變值,其本質是通過指針來改變變數的值;

補充:static修飾的弊端

<1>修改了變數的屬性類型——age由整型變數變成整型指針變數;

<2>static修飾的局部變數,是存放在數據區(全局區),直到整個程式結束才會釋放記憶體——不利於記憶體的有效利用;

 

2)設置為全局變數

此處就不論證,很容易理解,block對象代碼塊是放在另一個函數中,而該函數是可以訪問該全局變數的——這點沒問題;

 

3)__block修飾

//代碼

void test3()
{
    __block int age = 10;
    block = ^{
        age = 20;
        NSLog(@"%d", age);
    };
}

//列印

2019-01-15 16:51:56.337321+0800 MJ_TEST[4909:268533] 20
Program ended with exit code: 0

//clang

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};


struct __test3_block_impl_0 {
  struct __block_impl impl;
  struct __test3_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __test3_block_impl_0(void *fp, struct __test3_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void __test3_block_func_0(struct __test3_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref

        (age->__forwarding->age) = 20;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_3fa1c2_mi_2, (age->__forwarding->age));
    }


void test3()
{
    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
    block = ((void (*)())&__test3_block_impl_0((void *)__test3_block_func_0, &__test3_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
}

分析:

<1>在test3()方法中,被__block修飾的age變數被轉成__Block_byref_age_0類型的變數,而__Block_byref_age_0是一個結構體並且第一個成員變數是isa指針,那麼可以肯定__Block_byref_age_0類型age是一個OC對象——即經__block修飾的auto類型的局部變數會被系統生成一個新的OC對象;

<2>__Block_byref_age_0結構體中:__forwarding是一個指向該結構體本身的指針變數;age就是被捕獲到block結構體中的test3()方法中的age(ARC會被copy到堆區);

<3>在block對象的代碼塊函數__test3_block_func_0中,對整型變數age賦值流程:拿到block對象本身結構體中的成員變數age(__Block_byref_age_0類型指針變數)——>拿到新生成的OC對象結構體__Block_byref_age_0中的成員變數__forwarding——>拿到__Block_byref_age_0中的成員變數age;

補充:age->__forwarding->age <=> age->age,但是為什麼通過__forwarding(要多一道手續)來拿到最終的整型變數age呢?——該問題後面文章會寫到!

 

結論:通過__block修飾auto類型的局部變數來改變值,本質是系統會創建一個臨時的OC對象,該對象結構體存儲外部變數,而block對象結構體是通過該臨時對象來訪問外部變數;

補充:

<1> ARC模式強指針持有情況下,該OC臨時對象很顯然是存放在棧區——否則,test3()方法結束後block回調時,不能正確對age變數賦值(會崩潰)——此處涉及block的記憶體管理問題,後面文章會寫到!

<2>該方法並不會改變局部變數的類型,age其依然是atuo int類型;

<3>__block不能修飾static變數和全局變數

——因為__block就是為了在block代碼塊中修改外部auto類型的局部變數的值而設計的!

 

三、結論

【1】在block代碼塊中修改外部auto類型的局部變數的值:用static修飾、設置為全局變數、__block修飾;

【2】static修飾和設置為全局變數弊端:持續占有記憶體,不利於記憶體的高效利用;變數的生命周期不可控——__block反之;

【3】__block不能修飾static變數和全局變數;

註:以上對局部實例對象也適用——此處就不再論證了!

 

四、拓展——查找age地址值

//代碼

void test4()
{
    __block int age = 10;
    block = ^{
        age = 20;
        NSLog(@"%d", age);
    };
    
    NSLog(@"%p", &age);
}

//列印

2019-01-15 17:58:15.930081+0800 MJ_TEST[5583:308759] 0x100701f38
2019-01-15 17:58:15.930453+0800 MJ_TEST[5583:308759] 20
Program ended with exit code: 0

分析:列印出的age地址,據上述分析,到底是新生成的oc對象本身的地址,還是該對象結構體內成員變數age的值?

//代碼

struct __Block_byref_age_0 {
    void *__isa;
    struct __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

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

struct __test3_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(void);
    void (*dispose)(void);
};

struct __test4_block_impl_0 {
    struct __block_impl impl;
    struct __test3_block_desc_0* Desc;
    struct __Block_byref_age_0 *age;
};

//列印

 

分析:

<1>上述block的橋接轉換和&(strBlock->age->age),前面的文章已經講過,此處不再贅述;

<2>我們發現,__Block_byref_age_0結構體內的成員變數age的地址和test4()方法中列印出的age的地址是一樣的——也就是說,我們在OC代碼中對age的操作都是對__Block_byref_age_0結構體內的成員變數age的操作,這樣有利於程式員的理解(蘋果公司刻意隱藏底層)!

 

 

 GitHub


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

-Advertisement-
Play Games
更多相關文章
  • sysdate 【功能】:返回當前日期。 【參數】:沒有參數,沒有括弧 【返回】:日期 【示例】select sysdate hz from dual; 返回:2008-11-5 add_months(d1,n1) 【功能】:返回在日期d1基礎上再加n1個月後新的日期。 【參數】:d1,日期型,n1 ...
  • [oracle@sgdSSD-32 response]$ vi /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4::1 localhost localhost.localdo ...
  • 數值函數: 用來處理很多數值方面的運算,使用數值函數,可以免去很多繁雜的判斷求值的過程,能夠大大提高用戶的工作效率。 1、ABS(x):返回 x 的絕對值 2、CEIL(x):返回不小於 x 的最小整數,也就是說得大於或等於x的最小整數 同義詞:ceiling(x) 3、FLOOR(x):返回不大於 ...
  • 狂神聲明 : 文章均為自己的學習筆記 , 轉載一定註明出處 ; 編輯不易 , 防君子不防小人~共勉 ! mysql學習【第5篇】:事務索引備份視圖 MySQL事務 事務就是將一組SQL語句放在同一批次內去執行 如果一個SQL語句出錯,則該批次內的所有SQL都將被取消執行 MySQL事務處理只支持In ...
  • [TOC] 1. Android應用程式開發技術結構圖 一、 應用程式層 該層提供一些核心應用程式包,例如電子郵件、簡訊、日曆、地圖、瀏覽器和聯繫人管理等。同時,開發者可以利用Java語言設計和編寫屬於自己的應用程式,而這些程式與那些核心應用程式彼此平等、友好共處。 二、 應用程式框架層 該層是An ...
  • [TOC] 1.主目錄 1. .gradle和.idea 這兩個目錄下放置的都是Android Studio自動生成的一些文件,我們無須關心,也不要去手動編輯。 2. app: 項目中的代碼、資源等內容幾乎都是放置在這個目錄下的,我們後面的開發工作也基本都是在這個目錄下進行的,待會兒還會對這個目錄單 ...
  • 項目中、需要客戶端生成一個唯一的識別碼 ...
  • NSString *string = @"123"; // 1.字元串轉int int intString = [string intValue]; // 2.int轉字元串 NSString *stringInt = [NSString stringWithFormat:@"%d",intStri... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...