之前對__block的理解一直很模糊,然后學習了Notification Once,發現對__block的理解有待加強(雖然這篇文檔的重點不是這個)。這篇iOS中__block 關鍵字的底層實現原理,很好的講解了__block的底層實現。下面我講訴的是在學習了這篇文章的基礎上一點點個人理解。
需要理解的知識點:
- 常量存儲在常量區,全局變量/靜態變量存儲在全局區/靜態區。
變量:1.1 基本數據類型/非oc對象一般存儲在棧區,1.2 oc對象一般是存儲在堆區 - oc對象有指針概念,指針變量存儲在棧區,而指針存放的是對象在堆區的地址。所以我們是通過棧區的指針變量來索引堆中地址,從而訪問對象的。
- Block不允許修改外部變量的值,這里所說的外部變量的值,指的是棧中指針的地址。要想在Block內修改“外部變量”的值,必須被__block修飾。
1.基本數據類型(不被__block修飾)
int a = 100; //a在棧區
NSLog(@"棧中a的地址%p",&a);
void (^intBlock)(void) = ^(void){
//a = 33; Block不允許修改外部變量的值
NSLog(@"a = %d",a); //a在堆區
NSLog(@"堆中a的地址%p",&a);
};
a = 33; //a在棧區
intBlock();
---------------------------------------------------------
//輸出:
//棧中a的地址0x7fff5d5035ac
//a = 100
//堆中a的地址0x60c000256bb0
沒有被__block修飾的變量(棧區的a)被block持有(NSLog(@"a == %d",a);)就會被復制一份(在堆區生成一個a),那么在外面修改變量的值(a = 33;棧區的a),對block內的變量(堆區的a)不起作用(輸出 a == 100 不是33)。因為被復制后,這是兩個同名但地址不一樣的變量,所以對block中的a不影響。
2.oc對象(不被__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();
//番外的知識
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
*/
原理同上,但是又有區別。區別在于:
結合第2個知識點,str是指針變量,是存儲在棧區,"zw"才是被分配在堆區,而且block復制的是棧區的指針變量,而不是"zw"。復制后雖然棧區的str與堆區的str地址不一樣,但是它兩的存儲內容是一樣,都是"zw"的內存地址,指向的是同一單元對象。
另外str = @"once";這里實質是修改棧區str的存儲內容,將str重新指向堆中"once",因為系統會在堆中開辟新的地址給"once",并將str的內容由原先的"zw"內存地址換成"once"的內存地址。
NSMutableString *mutableStr = [NSMutableString stringWithString:@"zw"];
void (^mutableStrBlock)(void) = ^(void){
mutableStr.string = @"once";
NSLog(@"mutableStr = %@",mutableStr);
//mutableStr = [NSMutableString stringWithString:@"once"]; Block不允許修改外部變量的值
};
mutableStrBlock();
//輸出 mutableStr = once
是不是很奇怪為什么這里可以修改mutableStr,不是不能修改外部變量嗎?區別在于mutableStr.string = @"once";上面說到復制后雖然棧區的mutableStr與堆區的mutableStr地址不一樣,但是它兩的存儲內容是一樣,指向的都是"zw"。所以對mutableStr.string進行操作,實際是對堆中的對象單元進行操作,這是可以的。是不是更好理解第三點了。
3.基本數據類型/oc對象(被__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就會將“外部變量”在棧中的內存地址放到了堆中。無論是基本數據類型還是指針變量,都會被移到堆中,只會存在一個變量。這就是與沒有被__block修飾的區別所在。所以在block內修改,還是在block后面的外面修改都是修改堆中的變量。
至于Block什么不允許修改外部變量的值,iOS中__block 關鍵字的底層實現原理中有講到。然后使用block,要注意避免循環引用。以上如果有不正確的地方,歡迎指正,共同學習。