上一篇文章研究了一下Block的存儲域幔烛,這一篇文章我們來研究下__block變量的存儲域。
一稽莉、__block變量不能聲明為全局變量
當我們將__block聲明為全局變量的時候,代碼如下:
// 聲明為全局變量
__block int global_val = 10;
int main(int argc, const char * argv[]) {
...
return 0;
}
會報錯,報錯信息為__block attribute not allowed, only allowed on local variables
敬特。
為什么會報錯呢?其實也比較容易理解牺陶,__block這個屬性的出現(xiàn)就是為了解決Block內(nèi)部不能修改局部變量的問題伟阔。可是全局變量沒有這個問題掰伸,就不要多此一舉了皱炉。
二、__block變量的存儲域
從第一節(jié)的報錯信息__block attribute not allowed, only allowed on local variables
可以看出狮鸭,__block屬性只能用來修飾局部變量合搅,那么下面就引出了__block變量的存儲域以及Block從棧復制到堆時對__block變量產(chǎn)生的影響。
2.1 __block存儲在棧上
我們首先來想象一種場景歧蕉,__block屬性修飾的局部變量(非對象)灾部,從創(chuàng)建到到被棧BLock使用時,__block變量時存儲在哪個區(qū)域呢惯退?
先說答案赌髓,以下兩種情況,__block存儲在棧上:
- (非對象)剛初始化時;
- 被棧BLock使用時锁蠕。
2.1.1 (非對象)剛初始化時
__block變量剛初始化時的代碼如下:
int main(int argc, const char * argv[]) {
// 聲明為局部變量
__block int val = 10;
// 這個局部變量作為地址對比
int num = 10;
NSLog(@"__block變量的地址:%p -- 局部變量的地址:%p", &val, &num);
return 0;
}
控制臺打印語句如下:
__block變量的地址:0x7ffeefbff578 -- 局部變量的地址:0x7ffeefbff55c
我們可以看到夷野,__block變量的地址和普通局部變量的地址是挨著的,所以剛初始化時的__block變量存儲在棧上荣倾。
2.1.2 被棧Block使用
__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;
}
控制臺打印語句如下:
__block變量的地址:0x7ffeefbff588 -- 局部變量的地址:0x7ffeefbff56c
我們可以看到扫责,__block變量的地址和普通局部變量的地址是挨著的,所以被棧Block使用的__block變量存儲在棧上(__block變量沒有被強引用)逃呼。
2.2 __block存儲在堆上
我們知道了存儲在棧上的__block變量被棧BLock使用時鳖孤,__block變量并沒有拷貝到堆上,那么__block變量被堆BLock使用時抡笼,會發(fā)生什么呢苏揣?我們來探究一下。
上代碼:
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一下藏古,看看到底發(fā)生了什么:
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內(nèi)還是BLock外我們訪問的都是同一個__block變量。
2.2 __block被多個堆BLock使用
其實我們已經(jīng)探討好了__block變量的存儲域靡馁,就是棧和堆欲鹏。那么一個__block變量被多個堆Block使用時會發(fā)生什么呢?
其實__block變量本質上是一個對象臭墨,所以每被一個堆BLock使用時赔嚎,就代表被強引用一次,__block變量的引用計數(shù)+1胧弛,這個和OC的引用計數(shù)式內(nèi)存管理是完全一樣的尤误。
最后我們用一個表格來結束今天的文章。
BLock從棧賦值到堆時對__block變量產(chǎn)生額影響:
__block變量的配置存儲域 | BLock從棧賦值到堆時的影響 |
---|---|
棧 | 從椧镀裕拷貝到堆上并被Block持有袄膏,__forwarding指向堆上的__block對象 |
堆 | 被Block持有,引用計數(shù)+1 |