之前對(duì)__block的理解一直很模糊吃挑,然后學(xué)習(xí)了Notification Once揍诽,發(fā)現(xiàn)對(duì)__block的理解有待加強(qiáng)(雖然這篇文檔的重點(diǎn)不是這個(gè))籽慢。這篇iOS中__block 關(guān)鍵字的底層實(shí)現(xiàn)原理,很好的講解了__block的底層實(shí)現(xiàn)住诸。下面我講訴的是在學(xué)習(xí)了這篇文章的基礎(chǔ)上一點(diǎn)點(diǎn)個(gè)人理解驾胆。
需要理解的知識(shí)點(diǎn):
- 常量存儲(chǔ)在常量區(qū),全局變量/靜態(tài)變量存儲(chǔ)在全局區(qū)/靜態(tài)區(qū)贱呐。
變量:1.1 基本數(shù)據(jù)類型/非oc對(duì)象一般存儲(chǔ)在棧區(qū)丧诺,1.2 oc對(duì)象一般是存儲(chǔ)在堆區(qū) - oc對(duì)象有指針概念,指針變量存儲(chǔ)在棧區(qū)奄薇,而指針存放的是對(duì)象在堆區(qū)的地址驳阎。所以我們是通過(guò)棧區(qū)的指針變量來(lái)索引堆中地址,從而訪問(wèn)對(duì)象的。
- Block不允許修改外部變量的值呵晚,這里所說(shuō)的外部變量的值蜘腌,指的是棧中指針的地址。要想在Block內(nèi)修改“外部變量”的值饵隙,必須被__block修飾撮珠。
1.基本數(shù)據(jù)類型(不被__block修飾)
int a = 100; //a在棧區(qū)
NSLog(@"棧中a的地址%p",&a);
void (^intBlock)(void) = ^(void){
//a = 33; Block不允許修改外部變量的值
NSLog(@"a = %d",a); //a在堆區(qū)
NSLog(@"堆中a的地址%p",&a);
};
a = 33; //a在棧區(qū)
intBlock();
---------------------------------------------------------
//輸出:
//棧中a的地址0x7fff5d5035ac
//a = 100
//堆中a的地址0x60c000256bb0
沒(méi)有被__block修飾的變量(棧區(qū)的a)被block持有(NSLog(@"a == %d",a);)就會(huì)被復(fù)制一份(在堆區(qū)生成一個(gè)a),那么在外面修改變量的值(a = 33;棧區(qū)的a)金矛,對(duì)block內(nèi)的變量(堆區(qū)的a)不起作用(輸出 a == 100 不是33)劫瞳。因?yàn)楸粡?fù)制后,這是兩個(gè)同名但地址不一樣的變量绷柒,所以對(duì)block中的a不影響。
2.oc對(duì)象(不被__block修飾)
NSString *str = @"zw";
NSLog(@"1--棧中str的地址%p,str指向堆中的地址%p",&str,str);
void (^strBlock)(void) = ^(void){
//str = @"once"; Block不允許修改外部變量的值
NSLog(@"str = %@",str);
NSLog(@"3--堆中str的地址%p,str指向堆中的地址%p",&str,str);
};
NSLog(@"2--棧中str的地址%p,str指向堆中的地址%p",&str,str);
strBlock();
//番外的知識(shí)
str = @"once";
NSLog(@"4--棧中str的地址%p,str指向堆中的地址%p",&str,str);
--------------------------------------------------------
/*輸出:
1--棧中str的地址0x7fff5b9fa570,str指向堆中的地址0x1042051f0
2--棧中str的地址0x7fff5b9fa570,str指向堆中的地址0x1042051f0
str = zw
3--堆中str的地址0x60800024ad98,str指向堆中的地址0x1042051f0
4--棧中str的地址0x7fff5b9fa570,str指向堆中的地址0x108814290
*/
原理同上涮因,但是又有區(qū)別废睦。區(qū)別在于:
結(jié)合第2個(gè)知識(shí)點(diǎn),str是指針變量养泡,是存儲(chǔ)在棧區(qū)嗜湃,"zw"才是被分配在堆區(qū),而且block復(fù)制的是棧區(qū)的指針變量澜掩,而不是"zw"购披。復(fù)制后雖然棧區(qū)的str與堆區(qū)的str地址不一樣,但是它兩的存儲(chǔ)內(nèi)容是一樣肩榕,都是"zw"的內(nèi)存地址刚陡,指向的是同一單元對(duì)象。
另外str = @"once";這里實(shí)質(zhì)是修改棧區(qū)str的存儲(chǔ)內(nèi)容,將str重新指向堆中"once"株汉,因?yàn)橄到y(tǒng)會(huì)在堆中開(kāi)辟新的地址給"once"筐乳,并將str的內(nèi)容由原先的"zw"內(nèi)存地址換成"once"的內(nèi)存地址。
NSMutableString *mutableStr = [NSMutableString stringWithString:@"zw"];
void (^mutableStrBlock)(void) = ^(void){
mutableStr.string = @"once";
NSLog(@"mutableStr = %@",mutableStr);
//mutableStr = [NSMutableString stringWithString:@"once"]; Block不允許修改外部變量的值
};
mutableStrBlock();
//輸出 mutableStr = once
是不是很奇怪為什么這里可以修改mutableStr乔妈,不是不能修改外部變量嗎蝙云?區(qū)別在于mutableStr.string = @"once";上面說(shuō)到復(fù)制后雖然棧區(qū)的mutableStr與堆區(qū)的mutableStr地址不一樣,但是它兩的存儲(chǔ)內(nèi)容是一樣路召,指向的都是"zw"勃刨。所以對(duì)mutableStr.string進(jìn)行操作,實(shí)際是對(duì)堆中的對(duì)象單元進(jìn)行操作股淡,這是可以的身隐。是不是更好理解第三點(diǎn)了。
3.基本數(shù)據(jù)類型/oc對(duì)象(被__block修飾)
__block int b = 100;
__block NSString *strI = @"zw";
__block NSMutableString *strM = [NSMutableString stringWithString:@"zw"];
void (^block)(void) = ^(void){
b = 66;
strI = @"once";
strM = [NSMutableString stringWithString:@"once"];
NSLog(@"b = %d,strI = %@,strM = %@",b,strI,strM);
};
b = 99;
strI = @"ONCE";
strM = [NSMutableString stringWithString:@"ONCE"];
NSLog(@"b = %d,strI = %@,strM = %@",b,strI,strM);
block();
//輸出 b = 99,strI = ONCE,strM = ONCE,
//輸出 b == 66,strI = once,strM = once
__block修飾的變量block就會(huì)將“外部變量”在棧中的內(nèi)存地址放到了堆中唯灵。無(wú)論是基本數(shù)據(jù)類型還是指針變量抡医,都會(huì)被移到堆中,只會(huì)存在一個(gè)變量。這就是與沒(méi)有被__block修飾的區(qū)別所在忌傻。所以在block內(nèi)修改大脉,還是在block后面的外面修改都是修改堆中的變量。
至于Block什么不允許修改外部變量的值水孩,iOS中__block 關(guān)鍵字的底層實(shí)現(xiàn)原理中有講到镰矿。然后使用block,要注意避免循環(huán)引用俘种。以上如果有不正確的地方秤标,歡迎指正,共同學(xué)習(xí)宙刘。