日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS探索:Block解析浅谈

發(fā)布時間:2023/12/31 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS探索:Block解析浅谈 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

什么是Block

  • Block是將函數(shù)及其執(zhí)行上下文封裝起來的對象

接下來讓我們通過源碼來看一看Block的本質(zhì)

  • 我們在一個方法中寫了三行代碼,第一行是定義了一個局部變量,第二行是一個Block,第三行是這個Block的調(diào)用

這里我們通過一個clang的編譯命令clang -rewrite-objc xxx.m來看一下源碼的實現(xiàn)

  • 我們的那段代碼通過編譯器編寫后,首先第一行I代表的是一個實例方法后面的是對象和方法名,傳了兩個參數(shù)一個是self,一個是選擇器因子

  • 然后我們方法中的第一行代碼在編譯后沒有發(fā)生改變,我們著重看一下Block方法編譯后的改變

  • 首先我們可以看到__BlockOneObj__testMethod_block_impl_0這樣一個結(jié)構(gòu)體,在這個結(jié)構(gòu)體中傳遞了幾個參數(shù),第一個參數(shù)(void*)__BlockOneObj__testMethod_block_func_0我們通過名字可以知道這是一個無類型的函數(shù)指針,第二個參數(shù)&__BlockOneObj__testMethod_block_desc_0_DATA是一個Block相關(guān)描述的結(jié)構(gòu)體然后取地址符,第三個參數(shù)muIntNum就是我們定義的局部變量。最后取這個結(jié)構(gòu)體地址強(qiáng)制轉(zhuǎn)換賦值給我們定義的這個Block

然后我們來看看__BlockOneObj__testMethod_block_impl_0這個結(jié)構(gòu)體中有什么具體操作,如下圖

其中第一個結(jié)構(gòu)體里面又是什么數(shù)據(jù)結(jié)構(gòu)呢,請看下圖

在我們上面介紹的結(jié)構(gòu)體下面還有一個函數(shù),具體解釋請看下圖

那么什么是Block的調(diào)用呢

Block的調(diào)用其實就是函數(shù)的調(diào)用,從源碼中我們可以看出來

  • 首先先對這個Block進(jìn)行一個強(qiáng)制類型轉(zhuǎn)換(__block_impl *)Block

  • 之后又取出它之中的成員變量FuncPtr(函數(shù)指針),找到我們上面解析的結(jié)構(gòu)體和函數(shù),在其中拿到對應(yīng)的函數(shù)調(diào)用,然后把其中的參數(shù)傳遞進(jìn)去,一個參數(shù)是我們這個Block本身,一個是我們傳遞的2,然后就回去調(diào)用__BlockOneObj__testMethod_block_func_0函數(shù),最終進(jìn)行調(diào)用

Block截獲變量

首先我們先來看一段代碼

- (void)testMethod {int muIntNum = 6;int(^Block)(int) = ^int(int num){return num *muIntNum;};muIntNum = 4;Block(2); }復(fù)制代碼

這段代碼執(zhí)行完Block(2)返回的值是多少呢?-------答案是12 接下來我們看一下為什么是12以及Block截獲變量的本質(zhì)是什么

  • 對于基本數(shù)據(jù)類型的局部變量截獲其值

  • 對于對象類型的局部變量連同其所有權(quán)修飾符一起截獲

  • 對于局部靜態(tài)變量是以指針形式去截獲

  • 對于全局變量和靜態(tài)全局變量不截獲

下面直接上代碼

#import "BlockTwoObj.h"//全局變量 int global_var = 4; //全局靜態(tài)變量 static int static_global_var = 5;@implementation BlockTwoObj- (void)testMethodTwo {//基本數(shù)據(jù)類型的局部變量int var = 1;//對象類型的的局部變量__unsafe_unretained id unsafe_obj = nil;__strong id strong_obj = nil;//局部靜態(tài)變量static int static_var = 3;void(^Block)(void) = ^{NSLog(@"基本數(shù)據(jù)類型局部變量:%d", var);NSLog(@"對象類型局部變量(__unsafe_unretained修飾):%@", unsafe_obj);NSLog(@"對象類型局部變量(__strong修飾):%@", strong_obj);NSLog(@"局部靜態(tài)變量:%d", static_var);NSLog(@"全局變量:%d", global_var);NSLog(@"全局靜態(tài)變量:%d", static_global_var);};Block(); }@end 復(fù)制代碼

接下來我們通過clang命令clang -rewrite-objc -fobjc-arc xxx.m來看一下源碼

  • 在這張圖中可以很清晰的看到Block中的變量截獲,其中需要注意的是對于局部的靜態(tài)變量截獲的是指針,也就是說如果后面這個局部靜態(tài)變量發(fā)生了修改,那么Block中使用的是最新的值

__block修飾符

我們在什么情況下使用__block修飾符呢? 一般情況下,對被截獲變量進(jìn)行賦值操作需要添加__block修飾符,這里需要注意的是賦值不等于是使用,切記!!!

例如在下面的代碼中是否需要__block修飾符來修飾

NSMutableArray *muArr = [[NSMutableArray alloc] init];void(^Block)(void) = ^{//這里只是做了添加操作,并非賦值,所以不需要用__block進(jìn)行修飾[muArr addObject:@"111"];};Block(); 復(fù)制代碼

那么在下面的代碼段當(dāng)中呢?

__block NSMutableArray *muArrOther = nil;void(^BlockOther)(void) = ^{//這里做了賦值操作,所以需要用__block進(jìn)行修飾,否則會出現(xiàn)編譯報錯muArrOther = [NSMutableArray array];};BlockOther(); 復(fù)制代碼

對變量進(jìn)行賦值時

  • 需要__block修飾符修飾的是局部變量(包括基本數(shù)據(jù)類型和對象類型)

  • 不需要__block修飾符修飾的是靜態(tài)局部變量、全局變量和靜態(tài)全局變量,因為對于全局變量和靜態(tài)全局變量不涉及到變量的截獲,而對于靜態(tài)局部變量呢,是通過使用指針來操作對應(yīng)的變量的,所以也不需要修飾

下面請看一段代碼,還是我們上面的那個例子

- (void)testMethod {__block int muIntNum = 6;int(^Block)(int) = ^int(int num){return num *muIntNum;};muIntNum = 4;Block(2); } 復(fù)制代碼

此時Block返回的是8,這里是為什么呢,我們只是用了__block來修飾

  • 因為在這里會發(fā)生一個非常奇妙的變化,__block修飾的變量變成了對象

請看下面的流程圖

  • 首先__block int muIntNum會被轉(zhuǎn)化成第一個這樣一個結(jié)構(gòu)體,其中具有isa指針,我們也可以理解成一個對象

  • 從這個角度來看muIntNum經(jīng)過編譯后就會變成一個對象,通過__forwarding指針去找到對應(yīng)的對象,然后進(jìn)行賦值

  • 剛才我們看到的代碼段是在棧上,在__block變量中有一個__forwarding指針,而這個指針指向的是自己,這里要注意的是前提是在棧上,如果在堆上,這個__forwarding指針指向的就不是自己了,在下面會講到

  • 所以在棧上我們修改這個變量的值,就會通過__forwarding指針找到自己本省去修改這個變量的值

那么這里有一個問題就是我們在棧上這個__forwarding指向的是自己到底有什么用呢?我們完全可以通過訪問成員變量來修改,為什么還需要這個指針呢,請繼續(xù)往下看

Block的內(nèi)存管理

Block有三種類型

  • _NSConcreteGlobalBlock 全局Block

  • _NSConcreteStackBlock 棧Block

  • _NSConcreteMallocBlock 堆Block

Block的Copy操作

  • 當(dāng)我們棧上的Block通過copy在堆上產(chǎn)生一個一樣的Block,有相同的Block和__block變量,當(dāng)變量作用于結(jié)束后,棧上的Block對象就會被銷毀,而堆上的block依舊存在,所有如果棧上Block不用copy拷貝到堆上,在作用于銷毀后會因為找不到Block對象而崩潰

  • 當(dāng)然我們在這里有一個問題,假如說在MRC環(huán)境下,如果在棧上進(jìn)行了copy操作,會不會產(chǎn)生內(nèi)存泄漏,答案是肯定的,相當(dāng)于一個對象alloc出來,但是并沒有對應(yīng)的relese操作一樣

  • 當(dāng)我們棧上的Block經(jīng)過copy操作后,在堆上會產(chǎn)生一個一樣的Block,在棧中的Block中的__forwarding指針指向的事堆上Block的__block變量,并且在堆上Block的__forwarding指針也是指向的它自己的__block變量

參考書籍

Objective - C 高級編程:iOS與OS X多線程和內(nèi)存管理

Github

Demo

總結(jié)

以上是生活随笔為你收集整理的iOS探索:Block解析浅谈的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。