前言
在上一篇Objective-C Block,從0到1(1)中我已經對block的基本用法做了介紹,需要的朋友可以點擊查看。在這篇文章中我將重點介紹一下以下幾個方面:
- 如何在block中正確的使用非本地變量和實例變量
- block的內部結構
如果你對于以上的內容還不是很熟悉,那么歡迎你搭乘本次航班與一個七夕單身狗一起飛吧!
正文
一,變量不是你想變,想變就能變
首先讓我們看以下代碼:
int a = 5;
void (^ assignOtherNum) = ^ {
a = 10;
};
assignOtehrNum();
NSLog (@"%d",a);
以上的代碼定義了一個變量a和block。block的唯一作用就是改變a的賦值。
在繼續看以上代碼以前我們需要知道block的一點特性:
Inside of a block, you have access to same data as in a normal function: local variables, parameters passed to the block, and global variables/functions. But, blocks are implemented as closures, which means that you also have access to non-local variables. Non-local variables are variables defined in the block’s enclosing lexical scope, but outside the block itself.
以上的內容讓我們明確了以下幾點:
1, block可以像函數一樣存取相同的數據:本地變量,作為參數傳遞到block的數據,全局變量或函數。
2, block的實現是一個閉環,因此block可以對非本地變量進行存取。
3, 非本地變量是指那些被定義在block所在的語法范圍內的,但是又不是定義在block中的變量。在我們的代碼中,變量a就是一個非本地變量。
明白了以上幾點,我們再看看給出的代碼。那么NSLog到底會輸出什么呢?是10 嗎?
答案可能會讓你感到意外,我們的編譯器會給我們報錯:
為什么我們不能改變變量的賦值呢?
這是因為當我們在block中使用非本地變量的時候,這個變量被復制儲存在block中,并且這個變量是被當作const來使用。因此當我們嘗試對變量再次賦值的時候自然會讓編譯器報錯。
接下來,讓我們再看一段代碼:
NSString *brand = @"Apple";
NSString *(^brandFullName) (NSString *) = ^(NSString *model) {
return [brand stringByAppendingFormat:@"%@",model];
};
brand = @"Google";
NSLog(@"%@",brandFullName(@"Macbook Air"));
看了上面這段代碼,你覺得程序將輸出什么呢?
答案是:Apple Macbook Air
這是為什么呢?我們不是已經將brand重新賦值了嗎?
原因是,當我們將變量應用到block中的時候,這個變量的值會被block復制并且保存下來,如果我們在定義block之后重新對變量賦值,那么block中被復制并保存的值并不會被改變。
如何才能改變非本地變量的值?
__block int a; //__block修飾符
使用__block修飾符可以讓非本地變量在快中被修改。
__block int a = 5;
void (^ assignOtherNum) = ^ {
a = 10;
};
assignOtehrNum();
NSLog (@"%d",a);
這樣a的值就會變成10;
如果block所捕獲的變量是對象類型,那么它就會自動保留它。系統在釋放這個塊的時候也會將對象類型一并釋放。塊本身可視為對象,有引用計數。當最后一個指向塊的引用移走之后,塊就回收了?;厥諘r也會釋放塊塊所捕獲的變量,以便平衡捕獲時所執行的保留操作。
如果塊定義在Objective-C 類的的實例方法中,那么除了可以訪問類的所有實例變量之外,還可以使用self變量。塊總能修改實例變量,所以聲明時無需加__block。不過,如果通過讀取或者寫入操作捕獲了實例變量,那么也會自動把self一并捕獲,因為實例變量是與self所指代的實例關聯在一起的。
在上述情況下可能會產生保留循環,我將在后續的文章中具體講這個問題。
二,block的內部結構
塊作為對象也會占用內存區域,在存放塊對象的內存區域中,首個變量是指向Class對象的指針,該指針叫isa。
invoke -- 這是個函數指針,指向塊的實現代碼。
函數原型至少要接受一個void *類型的參數,這個參數代表塊。
descriptor變量 -- 指向結構體的指針,每個塊里都包含此結構體,其中聲明了塊對象的總體大小,還聲明了copy與dispose這兩個輔助函數所對應的函數指針。
快還會把它所捕獲的所有變量都拷貝一份。這些拷貝放在descriptor變量后面,捕獲了多少個變量,就要占據多少內存空間??截惖牟⒉皇菍ο蟊旧?,而是指向這些對象的指針變量。
因為在執行塊時從內存中把這些捕獲的變量讀出來,所以invoke函數要把塊對象當作參數傳進來。
后記
這里我們對于blok的有了更深的理解,后續我將繼續學習block的相關知識。如果你覺得這篇文章對你有用就點個贊吧!
相關參考資料
Ry’s Objective-C Tutorial
《Effective Objective-C 2.0 編寫高質量iOS與OS X代碼的52個有效辦法》