我們都說block會捕獲(持有)它使用到的區域性變數的值,可是它是如何實現捕獲自動變數的值的呢?
下面依然是使用一段**,然後用clang進行轉換,來分析其過程。
轉換前的main.m原始碼:
#import
intmain
(int argc,
const
char
* ar**)
; a =20;
blk();
}return0;
}
struct __main_block_impl_0 };
static
void
__main_block_func_0
(struct __main_block_impl_0 *__cself)
static
struct __main_block_desc_0 __main_block_desc_0_data =
;int
main
(int argc,
const
char
* ar**)
return0;
}
由此推斷,在__main_block_impl_0
中會多出其使用到的變數儲存位置,然後在其建構函式中,將使用到的區域性變數值儲存到結構體例項中。
__main_block_func_0
的實現過程,也發生了一些變化。
在使用區域性變數時,是先將其從__main_block_impl_0
例項的例項變數中取出,然後再使用這個自己儲存的值,所以不管外部如何修改,都不影響內部的這個值。
簡單總結下來,就是block中使用到的區域性變數,都會在編譯時動態建立的block實現結構體中建立乙個與區域性變數名稱一樣的例項變數,該例項變數儲存著外部的區域性變數的值,而當執行block時,再將這裡儲存下來的值取出來,所以這外部和block內部使用的是兩個不同的變數,因此即使外部先修改了外部變數的值,再執行block,也不會影響到block內的例項變數的值。
轉換前main.m中的測試**:
#import
intmain
(int argc,
const
char
* ar**)
; a =20;
blk();
}return
0;
轉換後的main.cpp中的block**:
struct __main_block_impl_0 };
static
void
__main_block_func_0
(struct __main_block_impl_0 *__cself)
static
struct __main_block_desc_0 __main_block_desc_0_data =
;int
main
(int argc,
const
char
* ar**)
return0;
}
從轉換後的**,可以看出與推斷的一致。
原始**:
#import
intmain
(int argc,
const
char
* ar**)
; name =
@"lisi"
; a =20;
blk();
}return0;
}
轉換後的**:
struct __main_block_impl_0 };
static
void
__main_block_func_0
(struct __main_block_impl_0 *__cself)
static
void
__main_block_copy_0
(struct __main_block_impl_0*dst,
struct __main_block_impl_0*src)
static
void
__main_block_dispose_0
(struct __main_block_impl_0*src)
static
struct __main_block_desc_0 __main_block_desc_0_data =
;int
main
(int argc,
const
char
* ar**)
return0;
}
從轉換後的原始碼,可以看出block中使用的物件型別,依然會在__main_block_impl_0
中新增乙個同樣型別的例項變數。然後依然是建立__main_block_impl_0
變數時,將物件對應的指標賦值給內部例項變數。後面變數時,也是用內部例項變數。
但是捕獲物件型別時,__main_block_desc_0
的結構體會有一些變化,多了兩個函式。這兩個函式分別是用來拷貝變數和釋放變數的。這個再後面篇章再介紹。
通過上面的轉換後的**,我們已經知道了,在block執行函式中,使用的是__main_block_impl_0
中儲存的例項變數。所以,如果我們修改這個例項變數的值,修改的也是__main_block_impl_0
中儲存的例項變數的值,無法修改到外部的區域性變數,而實際上,我們在block中是想修改外部的區域性變數的值,所以針對這種情況編譯器就給我們報錯,來提醒我們:
variable is not assignable (missing __block type specifier)
注意:這裡指的是修改沒有__block修飾符的區域性變數。那有沒有特殊情況呢?
答案是有的,有三種特殊的變數即使不使用__block,也可以在block中修改的:
我們依然使用clang來轉換一下試試看。
測試用的原始**:
#import
static
int static_global_value =1;
int global_value =2;
intmain
(int argc,
const
char
* ar**)
;blk()
;nslog
(@"static_global_value:%d---global_value:%d----static_value:%d"
,static_value);}
return0;
}
然後使用clang命令(clang -rewrite-objc main.m
)轉換。
轉換後的block**如下:
static
int static_global_value =1;
int global_value =2;
struct __main_block_impl_0 };
static
void
__main_block_func_0
(struct __main_block_impl_0 *__cself)
static
struct __main_block_desc_0 __main_block_desc_0_data =
;int
main
(int argc,
const
char
* ar**)
return0;
}
從以上**可以看出,block並不會捕獲全域性變數,而函式內的靜態變數,雖然block的結構體內部依然也有乙個變數,但是該變數儲存的確是靜態變數的指標,在執行block時,因為使用的是內部儲存的外部變數的指標,所以使用指標修改後,就等價於外部的靜態變數也被修改了。
那既然可以儲存變數的指標,block中為何儲存區域性變數的值,而不是儲存指標呢?
這跟記憶體區域有關!
我們知道記憶體區域有:全域性區、堆區、棧區、常量區、程式**區。
因為靜態變數和靜態全域性變數是儲存在全域性區,它們是在程式結束後,由系統釋放。所以出了函式作用域外,這些變數依然存在,那麼block中就依然可以正常使用。
但是區域性變數,雖然可以使用變數指標儲存,但是出了函式作用域,這些變數就被釋放了,那麼我們就不能在block中正常訪問了。
因此block中是建立乙個新的例項變數來儲存其捕獲的區域性變數,而不是建立乙個指標來儲存捕獲的區域性變數。
關於記憶體的5大分割槽和作用,簡要記錄一下:
棧區(stack)— 由編譯器自動分配釋放 ,存放函式的引數值,區域性變數的值等。
堆區(heap) — 一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由系統** 。arc下雖然不用我們管,其實是由於編譯器已經幫我們在合適的位置插入了retain/release/autorelease等而已。
全域性區(靜態區)(static)—全域性變數和靜態變數的儲存是放在一塊的。初始化的全域性變數和靜態變數在一塊區域, 未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。程式結束後由系統釋放。
文字常量區 —常量字串/const常量就是放在這裡的。 程式結束後由系統釋放。
程式**區—存放函式體的二進位制**。
詳細的可以看深入淺出-ios記憶體分配與分割槽
Block中 block實現原理
三.block中 block實現原理 我們繼續研究一下 block實現原理。1.普通非物件的變數 先來看看普通變數的情況。import int main int argc,const char ar myblock return 0 把上述 用clang轉換成原始碼。struct block byr...
springboot自動配置是如何實現的?
什麼是springboot自動配置?springboot的自動配置,指的是springboot會自動將一些配置類的bean註冊進ioc容器,我們可以需要的地方使用 autowired或者 resource等註解來使用它。自動 的表現形式就是我們只需要引我們想用功能的包,相關的配置我們完全不用管,sp...
Objc中block的實現
閉包 閉包是乙個函式 或者是指向函式的指標 再加上函式執行上下文的變數 有時候也稱做自由變數 block 實際上就是 oc語言對閉包的實現。block的資料結構定義如下 isaflags 用bit位 表示一些block的附加描述資訊 reserved 保留變數 invoke 函式指標 指向具體的bl...