上一篇文章研究了一下Block的存儲域,這一篇文章咱們來研究下__block變量的存儲域。bash
當咱們將__block聲明爲全局變量的時候,代碼以下:ui
// 聲明爲全局變量
__block int global_val = 10;
int main(int argc, const char * argv[]) {
...
return 0;
}
複製代碼
會報錯,報錯信息爲__block attribute not allowed, only allowed on local variables
。 爲何會報錯呢?其實也比較容易理解,__block這個屬性的出現就是爲了解決Block內部不能修改局部變量的問題。但是全局變量沒有這個問題,就不要畫蛇添足了。spa
從第一節的報錯信息__block attribute not allowed, only allowed on local variables
能夠看出,__block屬性只能用來修飾局部變量,那麼下面就引出了__block變量的存儲域以及Block從棧複製到堆時對__block變量產生的影響。code
咱們首先來想象一種場景,__block屬性修飾的局部變量(非對象),從建立到到被棧BLock使用時,__block變量時存儲在哪一個區域呢? 先說答案,如下兩種狀況,__block存儲在棧上:對象
__block變量剛初始化時的代碼以下:內存
int main(int argc, const char * argv[]) {
// 聲明爲局部變量
__block int val = 10;
// 這個局部變量做爲地址對比
int num = 10;
NSLog(@"__block變量的地址:%p -- 局部變量的地址:%p", &val, &num);
return 0;
}
複製代碼
控制檯打印語句以下:string
__block變量的地址:0x7ffeefbff578 -- 局部變量的地址:0x7ffeefbff55c
複製代碼
咱們能夠看到,__block變量的地址和普通局部變量的地址是挨着的,因此剛初始化時的__block變量存儲在棧上。it
__block變量被棧BLock使用的代碼以下:內存管理
int main(int argc, const char * argv[]) {
// 聲明爲局部變量
__block int val = 10;
// 這個局部變量做爲地址對比
int num = 10;
void (^__weak block)(void) = ^{
val = 11;
};
block();
NSLog(@"__block變量的地址:%p -- 局部變量的地址:%p", &val, &num);
return 0;
}
複製代碼
控制檯打印語句以下:table
__block變量的地址:0x7ffeefbff588 -- 局部變量的地址:0x7ffeefbff56c
複製代碼
咱們能夠看到,__block變量的地址和普通局部變量的地址是挨着的,因此被棧Block使用的__block變量存儲在棧上(__block變量沒有被強引用)。
咱們知道了存儲在棧上的__block變量被棧BLock使用時,__block變量並無拷貝到堆上,那麼__block變量被堆BLock使用時,會發生什麼呢?咱們來探究一下。 上代碼:
int main(int argc, const char * argv[]) {
// 聲明爲局部變量
__block int val = 10;
// 這個對象做爲地址對比
People *people = [[People alloc] init];
void (^block)(void) = ^{
val = 11;
};
block();
NSLog(@"__block變量的地址:%p -- 對象的地址:%@", &val, people);
return 0;
}
複製代碼
控制檯打印以下:
__block變量的地址:0x100704828 -- 對象的地址:<People: 0x100706b90>
複製代碼
咱們能夠看到,當存儲在棧上的__block變量被棧BLock使用時,__block變量被拷貝到了堆上(被堆BLock強引用)。 咱們把NSLog(@"__block變量的地址:%p -- 對象的地址:%@", &val, people);
這句代碼clang一下,看看到底發生了什麼:
NSLog((NSString *)&__NSConstantStringImpl__var_folders_0r_hkkmpct143n4wd3xxk0l1j8c0000gn_T_main_c842f2_mi_0, &(val.__forwarding->val), people);
複製代碼
重溫一下__block變量的結構體:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
複製代碼
咱們能夠看到,使用val
這個__block變量時,實際上是使用了val.__forwarding->val
這個值。
因此咱們能夠猜想,當__block變量初始化在棧上時,__forwarding
這個成員變量一開始指向的是棧上的__block變量,可是在__block變量拷貝了一份在堆上時,__forwarding
成員變量指向了堆上的__block變量。因此無論是在Block內仍是BLock外咱們訪問的都是同一個__block變量。
其實咱們已經探討好了__block變量的存儲域,就是棧和堆。那麼一個__block變量被多個堆Block使用時會發生什麼呢? 其實__block變量本質上是一個對象,因此每被一個堆BLock使用時,就表明被強引用一次,__block變量的引用計數+1,這個和OC的引用計數式內存管理是徹底同樣的。
__block變量的配置存儲域 | BLock從棧賦值到堆時的影響 |
---|---|
棧 | 從棧拷貝到堆上並被Block持有,__forwarding指向堆上的__block對象 |
堆 | 被Block持有,引用計數+1 |