iOS - block變量捕獲原理


block對變量的捕獲


1:可以捕獲不可以修改變量

  • 局部變量

2:可以捕獲且可以修改變量

  • 全局變量
  • 靜態變量
  • __block修飾的局部變量

原理分析:


1. 局部變量為什么可以被捕獲確不能修改

int a = 10;
void (^blcok)() = [^{NSLog(@"%d",a);
} copy];a=20;blcok(); // log : a = 10
復制代碼

結果應該大家都知道,但是為什么會這樣呢?

我們用clang轉化之后看看


從block定義來看

void (*blcok)() = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, a)), sel_registerName("copy")); 
復制代碼

block的實現是通過__ZMX__blockTest_block_impl_0結構體的構造方法來定義的,我們來看下這個結構體

struct __ZMX__blockTest_block_impl_0 {struct __block_impl impl;struct __ZMX__blockTest_block_desc_0* Desc;int a;__ZMX__blockTest_block_impl_0(void *fp, struct __ZMX__blockTest_block_desc_0 *desc, int _a, int flags=0) : a(_a) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
復制代碼

impt:

struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};
復制代碼
isa:指向Class的指針
flags:一些標識
reserced:保留的一些變量
funcptr:函數指針
復制代碼

__ZMX__blockTest_block_desc_0:

static struct __ZMX__blockTest_block_desc_0 {size_t reserved;size_t Block_size;
} __ZMX__blockTest_block_desc_0_DATA = { 0, sizeof(struct __ZMX__blockTest_block_impl_0)};
復制代碼
reserced:保留的一些變量
size:內存大小
復制代碼

__ZMX__blockTest_block_impl_0 構造方法

我們可以看到這個構造方法有四個參數

void *fp:函數指針
struct __ZMX__blockTest_block_desc_0 *desc: desc結構體
int _a: 變量
int flags=0:標識 可以不傳
復制代碼

我們通過簡化block的定義:

void (*blcok)() = ((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, a));
復制代碼

可以看到,我們在定義的時候就已經將a作為參數傳遞進去了。也就是在定義的時候我們的block就獲取到了a的值,而且不管后面怎么修改a的值。我們在block內部獲取的a都是定義的時候傳進來的值,這也就導致為什么block可以捕獲局部變量卻不可以修改的原因


2.1 全局變量 可以被捕獲也可以修改

(void)blockTest
{void (^blcok)() = [^{NSLog(@"%d",a);} copy];a = 20;blcok(); // log : 20} 
復制代碼

我們用clang轉化之后看看

一樣的部分我就不重復了,我們可以看到這個時候定義blcok的構造函數是沒有傳入之前的參數a

我們調用block然后再去執行NSLog函數 = 上面__ZMX__blockTest_block_func_0函數,這時候a的值已經改為20了

static void __ZMX__blockTest_block_func_0(struct __ZMX__blockTest_block_impl_0 *__cself) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6nlw9jbn3fb7c8lb1km1rzmm0000gn_T_ZMX_70ee3a_mi_0,a);}復制代碼

很顯然,在我們調用block的時候,如果你之前有修改a的值,那打印的一定是新值


2.2 靜態變量 可以被捕獲也可以修改

 (void)blockTest
{static int a = 10;void (^blcok)() = [^{NSLog(@"%d",a);} copy];a = 20;blcok(); //log : 20}
復制代碼

我們用clang轉化之后看看

通過構造函數我們可以看到,這時候入參多了一個int *_a,傳遞的是a的地址了。打印的函數__ZMX__blockTest_block_func_0也一樣,都是獲取到同一內存地址上的值操作。so,我們既可以訪問a同時也可以修改a了


2.3 __block修飾的變量 可以被捕獲也可以修改

(void)blockTest
{__block int a = 10;void (^blcok)() = [^{NSLog(@"%d",a);} copy];a = 20;blcok();// log : 20}
復制代碼

我們用clang轉化之后看看

哎!這時候的結構體__ZMX__blockTest_block_impl_0a變成了一個結構體指針。好奇怪,我們來看一下這個結構體

struct __Block_byref_a_0 {void *__isa;
__Block_byref_a_0 *__forwarding;int __flags;int __size;int a;
};
復制代碼
isa: 指向Class指針
forwarding: 是指向a地址的指針
flags:標識
size:大小
a: 變量
復制代碼

我們再來看一下 我們blockTest函數

static void _I_ZMX_blockTest(ZMX * self, SEL _cmd) {__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};void (*blcok)() = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)), sel_registerName("copy"));(a.__forwarding->a) = 20;((void (*)(__block_impl *))((__block_impl *)blcok)->FuncPtr)((__block_impl *)blcok);}
復制代碼

這時候變量a變成了一個__Block_byref_a_0結構體,可以看到我們初始化的時候給a的地址跟a的值都傳進去了

a = 20 -> (a.__forwarding->a) = 20 再次賦值我們是通過修改a指向的內存地址上的value來修改a的值

打印函數

static void __ZMX__blockTest_block_func_0(struct __ZMX__blockTest_block_impl_0 *__cself) {__Block_byref_a_0 *a = __cself->a; // bound by refNSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6nlw9jbn3fb7c8lb1km1rzmm0000gn_T_ZMX_c9e1ad_mi_0,(a->__forwarding->a));}
復制代碼

我們是通過先獲取block捕獲到的a的內存地址對應的value,然后打印出來

所以我們可以捕獲并且修改a的值




筆者是一個剛入門iOS,對block的原理一直是望而卻步。

這次終于鼓足干勁努力嘗試一番,一定有很多的不足,希望大家不吝賜教!

有任何問題可以留言,或者直接聯系QQ:346658618

希望可以相互學習,一起進步!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/284889.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/284889.shtml
英文地址,請注明出處:http://en.pswp.cn/news/284889.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Shell 更好看的回顯

#!/bin/shsource /etc/init.d/functionsaction "hello" /bin/true轉載于:https://blog.51cto.com/itech/1768218

【ArcGIS風暴】ArcGIS中等高線高程標注/注記(打斷/消隱)方法案例匯總

本文以案例的形式,圖文并茂詳細講解在ArcGIS 10.6中,等高線高程標注、注記的方法。 文章目錄 一、屬性標注二、Maplex工具標注1. 使用Maplex標注引擎2. 標注轉換為注記3. 要素輪廓線掩膜4. 使用掩膜選項進行繪制參考閱讀: 【CASS精品教程】CASS9.1等高線的繪制完整案例教程 …

Blazor University (35)表單 —— 編寫自定義驗證

原文鏈接:https://blazor-university.com/forms/writing-custom-validation/編寫自定義驗證源代碼[1]請注意,與有關 EditContext、FieldIdentifiers 和 FieldState[2] 的部分一樣,這是一個高級主題。如前所述,FieldState 類保存表…

HTML 元素內部添加預加載

CSS: /*元素內部加載loading*/.innerLoading {height: 100%;width: 100%;display: flex;justify-content: center;align-items: center;}.innerLoading * {text-align: center;color: #737782cc;fill: #73777A;font-size: 1em !important;font-family: SimSun,SimHe…

Windows下怎樣安裝Tomcat

Tomcat 是開源的WEB應用容器,所以受到各位程序員和公司的親賴。在這里給大家介紹一下如何在Windows環境下安裝Tomcat綠色版本,希望能夠對大家有幫助。 1.首先去Tomcat官網下載Tomcat軟件,在百度中搜索Tomcat,進入英文網址http://tomcat.apach…

智能識別云服務端平臺之神【合合信息TextIn】

一、前言 眾所周知,隨著互聯網和人工智能的發展,我們非常多的場景需要用到智能“識別”功能,比如人臉識別、通用文字識別、表格識別、辦公文檔識別、身份證、名片、營業執照等國內外卡證文字識別等等,同時識別與理解面臨的全球性技…

【ArcGIS微課1000例】0015:ArcGIS如何創建/自定義快捷鍵?

為了提高工作效率,強大的ArcGIS提供了很多快捷鍵,如訪問 ArcMap 菜單命令、窗口操縱、刷新或暫停地圖繪制、通過拖放進行移動或復制等等。本文在ArcGIS已有快捷鍵的基礎之上,為了提高工作效率,講解如何定制個性化的快捷鍵。 參考閱讀:【ArcGIS風暴】ArcGIS快捷鍵大全 文章…

Bresenham 算法

1965 年,Bresenham 為數字繪圖儀開發了一種繪制直線的算法,該算法同樣使用于光柵掃描顯示器,被稱為 Bresenham 算法。 原理 算法的目標是選擇表示直線的最佳光柵位置。Bresenhan 算法在主位移方向上每次遞增一個單位。另一個方向的增量為 0…

Python高級特性——迭代(Iteration)

Python高級特性——迭代(Iteration) 1、給定一個集合list或者tuple,可以通過for …… in ……的語法來實現循環遍歷,這個循環我們就叫做迭代 迭代list: >>> m [haha,hehe,heihei,gaga] >>> for li …

ML.NET 更新

點擊上方藍字關注我們(本文閱讀時間:5分鐘)ML.NET是一款面向.NET開發人員的開源,跨平臺機器學習框架,可以將自定義機器學習集成到.NET應用中。我們很開心地向您介紹我們在過去幾個月中所做的工作。ML.NET:https://dotnet.microsof…

Andriod之提示java.lang.SecurityException: getDataNetworkTypeForSubscriber導致程序奔潰

1、問題 修改targetSdkVersion 33 適配Android13后4G網絡環境被其它app拉起來提示這個異常 2、原因 我們定位到代碼在這行函數 telephonyManager.getNetworkType()Android11 的權限有關,由于缺少該權限導致無法訪問接口而提示安全異常 3、解決辦法 方法1:我們直接申請RE…

js-權威指南學習筆記7

第七章 數組 1、數組直接量的語法允許有可選的結尾的逗號,所以[ , , ]只有兩個元素而非三個。 2、調用構造函數Array()創建數組時,傳入一個參數時表示指定數組的長度。 3、所有的索引都是屬性名,但只有在0~2^32-2之間的…

[譯]基于GPU的體渲染高級技術之raycasting算法

[譯]基于GPU的體渲染高級技術之raycasting算法 PS:我決定翻譯一下《Advanced Illumination Techniques for GPU-Based Volume Raycasting》。像我翻譯其他資料一樣,只按我的需要和觀點來翻譯。有的部分詳細翻譯,附加注解,有的部分…

【GIS風暴】什么是地理空間智能(Geospatial AI)?

人工智能(Artificial Intelligence,AI)已經成為新技術革命下一階段的熱詞,也成為未來產業的驅動力量。使用智能算法,數據分類和智能預測、分析,AI在很多領域將有一系列的工具來幫助解決問題。 將AI用于GIS這一具體的領域的分析、方法和解決方案,就叫地理空間智能(Geos…

JavaScript 清除圖片背景顏色 使之透明

主要JS /**清除圖片背景顏色 **/ function removeImgBg(img) {//背景顏色 白色const rgba [255, 255, 255, 255];// 容差大小const tolerance 60;var imgData null;const [r0, g0, b0, a0] rgba;var r, g, b, a;const canvas document.createElement(canvas);const cont…

day01基礎部分

一、python是什么樣的語言 1、編譯型語言和解釋型語言,python是解釋型語言 1.1、編譯型語言就是把源程序代碼一次性翻譯成機器碼(計算機可識別的代碼),然后交給計算機去運行,一般需經過編譯(compile&#x…

WPF 制作 Windows 屏保

分享如何使用WPF 制作 Windows 屏保WPF 制作 Windows 屏保作者:驚鏵原文鏈接:https://github.com/yanjinhuagood/ScreenSaver框架使用.NET452;Visual Studio 2019;項目使用 MIT 開源許可協議;更多效果可以通過GitHub[1]|碼云[2]下…

Java 定時線程

功能需求:項目啟動時,后天起一個定時線程,每個小時跑一次,查出數據發郵件出來。 主要使用 public void schedule(TimerTask task, long delay)task被安排在delay(毫秒)指定的時間后執行。 public void sche…

Windows 7 下右鍵發送到菜單項沒了

為什么80%的碼農都做不了架構師?>>> 問題描述: 突然有一天,Windows 7 下右鍵發送到菜單項沒了,如圖所示: 問題原因 黑人問號臉? 轉載于:https://my.oschina.net/taadis/blog/1591398

【ArcGIS微課1000例】0016:ArcGIS書簽操作(添加書簽、管理書簽)知多少?

書簽可以將地圖數據的某一視圖狀態保存起來,以便在使用時打開書簽,直接回到這一視圖狀態。可創建多個書簽以便快速回到不同的視圖狀態,也可以對書簽進行管理。 文章目錄 1 創建書簽2 管理書簽注意:書簽只針對空間數據,在【布局視圖】中是不能創建書簽的。 1 創建書簽 可…