自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深究Block的實(shí)現(xiàn)

移動開發(fā)
從編譯過后的代碼中,去理解ios中的block是怎么實(shí)現(xiàn)的

[[163549]]

先看一下Block使用的語法

聲明一個(gè)block
返回值 (^名稱)(參數(shù)列表) = ^(參數(shù)列表){
};

  1. int (^name)(int ,int) = ^(int a,int b){ 
  2.       
  3.      return (a+b); 
  4. }; 

作為一個(gè)函數(shù)的參數(shù):

  1. - (void)testBlock:(NSString *(返回類型) (^)(int a))s(block名字) { 
  2. NSString *a = s(1); 
  3.    
  4.  [self testBlock:^NSString *(int a) { 
  5.       
  6.      a = 5
  7.       
  8.      return @"1"
  9. }]; 

然后通過底層代碼分析一下block的實(shí)現(xiàn)

iOS中有三種block,下文會細(xì)說
NSConcreteGlobalBlock;//在全局中定義的
NSConcreteStackBlock; //在局部定義的
NSConcreteMallocBlock;//分配在堆中

先從最簡單的看起

  1. int main(int argc, char * argv[]) { 
  2. void (^block)(void) = ^{     
  3.   NSLog(@"1"); 
  4. }; 

(通過 在終端找到這個(gè).m文件,然后clang -rewrite-objc 代碼文件名 就可以看到文件夾有個(gè).cpp的文件,本來想變量ViewController的文件,里面用了UIKit庫,編譯的時(shí)候總是顯示找不到,于是我編譯的main.m文件)

編譯過來是

block源碼

這里先來分析一下(便于理解):

  1. static void _main_block_func_0(struct _main_block_impl_0 *__cself) { 
  2.  NSLog((NSString *)&__NSConstantStringImpl__var_ 
  3. folders_yq_s_hjnhd12x79wq1ldg1jdr_w0000gn_T_main_50d1d6_mi_0); 

可以看到,這里對應(yīng)我們代碼中的block中的實(shí)現(xiàn),所以可以知道,block使用的匿名函數(shù),實(shí)際上被當(dāng)作一個(gè)函數(shù)來處理。不過傳入的是:一個(gè)_main_block_impl_0類型的結(jié)構(gòu)體,里面有一個(gè)block_impl的結(jié)構(gòu)體,和一個(gè)_main_block_desc_0的結(jié)構(gòu)體。跟著是他們的構(gòu)造函數(shù)。
來簡單看一下這個(gè)_main_block_impl_0結(jié)構(gòu)體吧:

isa指向這個(gè)block的類型。這里說明這個(gè)block是NSConcreteStackBlock類型的。
flag是標(biāo)志,可以看到,默認(rèn)構(gòu)造為0;
還有一個(gè)FuncPtr,也就是指向函數(shù)地址的指針。
還有一個(gè)__main_block_desc_0的結(jié)構(gòu)體,
在下面可以看到這個(gè)結(jié)構(gòu)體的初始化,
一個(gè)是reserverd默認(rèn)為0,
一個(gè)是block_size。是這個(gè)impl的size。
所以,這個(gè)_main_block_impl_0,我們可以理解為就是一個(gè)block實(shí)例,里面的成員變量有要執(zhí)行的函數(shù)的指針,和isa(和所有的oc對象一樣),還有一個(gè)size。

現(xiàn)在看一下main函數(shù)里面的內(nèi)容

  1. void (*name)(void) = ((void (*)())&__main_block_impl_0((void  
  2. *)__main_block_func_0, &__main_block_desc_0_DATA)); 

這里執(zhí)行的操作就是:初始化一個(gè)block實(shí)例,交給我們這么name名字變量,也就是用name這個(gè)指針指向這個(gè)block實(shí)例,執(zhí)行的時(shí)候,直接找到這個(gè)block中的指向函數(shù)地址的指針。

通過以上可以了解:block的實(shí)質(zhì),就是一個(gè)對象,包含了一個(gè)指向函數(shù)首地址的指針,和一些與自己相關(guān)的成員變量。

接著看一下block訪問外部變量是怎么回事,這也是我們最關(guān)心的問題。

先來看一下局部變量:

  1. int a = 10
  2. int b = 20
  3. void (^block)() = ^ { 
  4.    NSLog(@"%d--%d",a,b); 
  5. }; 
  6. block (); 

block訪問局部變量

首先在_main_block_impl_0的定義中看到,block中用到的變量被作為成員變量追加到了結(jié)構(gòu)體中,

  1. _main_block_impl_0(void *fp, struct _main_block_desc_0 *desc,  
  2. int _a, int _b, int flags=0) : a(_a), b(_b) { 
  3. impl.isa = &_NSConcreteStackBlock; 
  4. impl.Flags = flags; 
  5. impl.FuncPtr = fp; 
  6. Desc = desc; 

在初始化的時(shí)候,也需要把a(bǔ)和b的值傳入。

  1. void (**block)() = ((void (*)())&_main_block_impl_0((void *) 
  2. _main_block_func_0, &_main_block_desc_0_DATA, a, b)); 

這里看到,在初始化的時(shí)候,把a(bǔ),b值傳入來初始化一個(gè)_main_block_impl_0結(jié)構(gòu)體。這里需要注意,傳入的是a和b的值,所以我們要在結(jié)構(gòu)體中改變a和b是無效的。并且編譯會報(bào)錯(cuò),如果要在block中改變里面的值。有以下幾種方法

1,靜態(tài)變量,全局變量

直接上代碼吧,嘻嘻

  1. int global_val  = 1
  2. static int static_global_val = 2
  3. int main(int argc, char * argv[]) { 
  4. static int static_val = 3
  5. void (^block)() = ^ { 
  6.     global_val = 10
  7.     static_global_val = 20
  8.     static_val = 30
  9. }; 
  10. NSLog(@"%d-%d-%d",global_val,static_global_val,static_val); 
  11. block (); 
  12. NSLog(@"%d-%d-%d",global_val,static_global_val,static_val); 

然后rewrite一下

訪問全局變量

因?yàn)檫@個(gè)_main_block_func_0能直接訪問靜態(tài)變量,所以可以直接訪問這個(gè)變量的值,也可以改變,不用擔(dān)心在調(diào)用這個(gè)函數(shù)的時(shí)候,全局變量訪問不到,導(dǎo)致錯(cuò)誤。所以這里面在調(diào)用全局變量的時(shí)候,就是很普通的調(diào)用全局變量,沒有什么不同。

局部靜態(tài)變量則傳的是一個(gè)指針進(jìn)來。因?yàn)椴挥脫?dān)心它在函數(shù)結(jié)束時(shí)或者其他什么地方被釋放,所以可以放心的訪問這個(gè)值。

  1. global_val = 10
  2.    static_global_val = 20
  3.    (*static_val) = 30

改變的是這個(gè)指針指向的值

  1. void (*block)() = ((void (*)())&__main_block_impl_0((void *) 
  2. _main_block_func_0, &__main_block_desc_0_DATA, &static_val)); 

可以看到,這里傳的是指針。
_main_block_func_0的作用域在main函數(shù)之外,要訪問這個(gè)變量,就只能傳指針。

第二種方法是給參數(shù)加__block屬性

  1. int main(int argc, char * argv[]) { 
  2. __block int block_val = 3
  3. void (^block)() = ^ {         
  4.  block_val = 30
  5. }; 
  6. NSLog(@"%d",block_val); 
  7. block (); 
  8. NSLog(@"%d",block_val); 
  9. NSLog(@"%@",block); 

__block參數(shù)

可以看到里面比其他多了一個(gè)這樣的結(jié)構(gòu)體_Block_byref_block_val_0
里面有:

_isa初始化為0,
__forwarding;//持有該實(shí)例自身的的指針
int flags;為0
int s__Block_byref_block_val_0ize;size
int block_val;//存放這個(gè)變量的值
原來是把一個(gè)局部變量,封裝成了一個(gè)結(jié)構(gòu)體
賦值的時(shí)候直接給這個(gè)結(jié)構(gòu)體中的這個(gè)值賦值
(block_val->__forwarding->block_val) = 30;

所以,在訪問這個(gè)變量的時(shí)候,其實(shí)在訪問這個(gè)結(jié)構(gòu)體的這個(gè)變量。

關(guān)于_forwarding的作用請不要著急。

Block有三種類型:

  1. { 
  2. NSConcreteGlobalBlock;//在全局中定義的 
  3. NSConcreteStackBlock; //在局部定義的 
  4. NSConcreteMallocBlock;//分配在堆中 
  5. } 

設(shè)置在棧上的block,當(dāng)“name這個(gè)名字變量”作用域結(jié)束時(shí),block變量也會廢棄。
所以,iOS提供了將block結(jié)構(gòu)體和_block變量,復(fù)制到堆上的方法。即使block的name變量結(jié)束,那么堆上的block還可以繼續(xù)訪問。
而此時(shí),_block變量結(jié)構(gòu)體中的_forwarding變量可以實(shí)現(xiàn),無論在堆上還是在棧上。都可以正確訪問_block變量。可以理解,當(dāng)把_block變量復(fù)制到堆上的時(shí)候,_forwarding就指向堆里中的自己。所以無論是訪問棧中自己,還是堆中的自己,最終訪問都是堆中的這個(gè)值。

一個(gè)Block對_block的內(nèi)存管理方式與 ARC機(jī)制完全相同。
而_main_block_desc 中的copy和dispose就是這個(gè) __block的retain和release操作。

那什么block在時(shí)候會復(fù)制到堆呢?

  1. 掉用block的copy方法。
  2. block作為函數(shù)返回值返回時(shí)。
  3. block調(diào)用外面的_strong的id的類時(shí),或用_block時(shí)。
  4. 方法中,用usingblock或者GCD中的API時(shí)。

這里想講一下,在局部函數(shù)里,定義block時(shí),打印出來還是NSConcreteGlobalBlock類型的,而且只要用了外部變量,不管是assign還是week還是strong類型的,打印出來都是NSConcreteMallocBlock類型的。所以我猜測這會不會是蘋果新版的改進(jìn),為了block在訪問無效的變量,直接把block拷貝到堆上,從而也拷貝一份變量。或許是我忽略了中間的某個(gè)步驟

其實(shí)到了這里,不用再描述,也知道為什么會發(fā)送死循環(huán),又怎么解決了。當(dāng)在block中用self的時(shí)候,block拷貝到堆上,首先,在棧上的這個(gè)block有一個(gè)持有者,是name這個(gè)變量。當(dāng)name這個(gè)變量作用域之外,棧上這個(gè)block就release了。,那么當(dāng)block拷貝到堆上的時(shí)候,block有一個(gè)持有者是self,那么block在拷貝時(shí),它的變量一個(gè)self指針,也會拷貝,而self又指向這個(gè)block,block持有self,self持有block,兩者都不會釋放。要打破這個(gè)循環(huán),需要將self置為__week,就算拷貝一個(gè)week指針,那也不影響self的引用計(jì)數(shù)。

責(zé)任編輯:倪明 來源: CocoaChina
相關(guān)推薦

2010-12-20 15:25:36

編譯時(shí)間.NET

2022-11-11 10:56:37

2013-05-29 15:33:01

開源虛擬化KVM

2013-04-08 10:08:22

開源虛擬化KVM

2013-04-07 09:33:31

開源虛擬化KVM

2020-11-10 15:25:26

SemaphoreLinux翻譯

2021-12-31 07:48:58

Vue3 插件Vue應(yīng)用

2013-06-04 15:41:31

iOS開發(fā)移動開發(fā)block

2021-12-18 20:46:38

亞馬遜云科技阿里云IaaS

2017-03-07 09:45:43

iOSBlock開發(fā)

2014-07-30 11:12:09

block

2018-01-04 10:08:08

2010-09-16 09:13:09

CSS display

2010-04-07 16:54:55

Oracle性能

2025-01-10 09:47:43

blockSDKiOS

2010-09-03 12:55:15

CSSblockinline

2010-01-06 16:22:53

第三層交換技術(shù)

2014-07-31 16:47:10

block

2017-05-12 17:58:11

2011-07-29 16:16:30

Objective-c block
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號