前言
先前我曾在在Objective-C中使用Block,以及在Block中存取Block外定義的變數之探討簡要提及了Block的概念。
而此篇要稍微深入探討使用Block時應注意的事項。
Block所佔用的記憶體區域被配置在堆疊上
考慮以下程式碼:
void (^block)();
if {
block = ^{ //do something 1... };
}
else {
block = ^{ //do something 2... };
}
block();
我們可能很容易因邏輯需求而寫出這樣的程式碼。
然而,由於Block所佔用的記憶體區域被配置在堆疊上;因此,Block所占用的記憶體空間只在該作用域內有效。 在C語言中,即便是在同一個函式中,一個大括號包起來的就是一個獨立的作用域[1];這表示,過了該作用域之後,Block所占用的記憶體空間可能不再被保證有效。
程式可能出錯,也可能不會;端看該Block所占用的記憶體空間是否被覆蓋掉
。
不過,一個簡單的做法就可以解決此問題,使用copy[2]:
void (^block)();
if {
block = [^{ //do something 1... } copy];
}
else {
block = [^{ //do something 2... } copy];
}
block();
如果指向該Block的變數是物件的property中,也應該使用copy:
@property (copy) void (^myblock)(void);
以copy建立副本,確保存放該Block的變數,其指向的block是有效的。
避免Block隱式保留self而造成的保留循環
我曾在前文提及,
Block會隱式地保留self:
@implementation ViewController {
int myValue;
}
- (void)viewDidLoad
{
myValue = 123;
void(^myBlock)(void) = ^{
//其實是存取self->myValue, block隱式保留self
NSLog(@"myValue in block:%i",myValue);
};
}
由於Block也是Objective-C物件,也會被參考計數;因此,如下寫法:
@interface ViewController : UIViewController
@property (copy) void (^myblock)(void);
@end
@implementation ViewController {
int myValue;
}
- (void)viewDidLoad
{
myValue = 123;
self.myblock = ^{
//存取self->myValue, block隱式保留self
NSLog(@"myValue in block:%i",myValue);
};
}
會使myblock保留self,而self本身又擁有myblock,兩個物件互相參考, 造成所謂的保留循環,使兩者的記憶體永遠不會被釋放。
解法1:使用weak參考的self
@interface ViewController : UIViewController
@property (copy) void (^myblock)(void);
@end
@implementation ViewController {
int myValue;
}
- (void)viewDidLoad
{
myValue = 123;
ViewController* __weak weakSelf = self;
self.myblock = ^{
//確認self尚未被取消配置
if(weakSelf != nil) {
NSLog(@"myValue in block:%i",weakSelf->myValue);
}
};
}
使用另一個變數指向self,並使用__weak修飾子, 如此一來,該變數(weakSelf)不會保留住self(不會增加參考計數)。
而Block使用的是weakSelf而非self本身,因此不會保留self。
唯一要注意的是,在self被取消配置時,weakSelf會被設為nil; 因此在執行前,需要檢查一下weakSelf是否為nil(雖然在正確的邏輯下不應該發生); 做好例外處理。
解法2:在block執行完後,將指向block的property設為nil
@interface ViewController : UIViewController
@property (copy) void (^myblock)(void);
@end
@implementation ViewController {
int myValue;
}
- (void)viewDidLoad
{
myValue = 123;
self.myblock = ^{
//存取self->myValue, block隱式保留self
NSLog(@"myValue in block:%i",myValue);
self->myblock = nil;
};
}
更簡單的方法是在執行完block的時候,將myblock設為nil,解除互相參考的狀態。 然而,倘若block永遠都沒有執行,則此保留循環的狀況也將不會解除。 因此,務必確認block一定會被執行;否則,建議使用解法1。
結語
Block的使用一直都是把雙面刃,如果你不夠了解Block的運作機制以及其行為模式,當然很有可能造成非預期的錯誤。 Block的語法乍看之下複雜,不過其實並不難。如果你先前曾有一些Script語言的經驗,諸如: JavaScript的函式實字,或是Python的lambda表示式...等等,則應該不難理解Block的概念。
在iOS程式開發中,許多處理多執行緒的API,也漸漸地使用Block,如GCD, NSBlockOperation...等等,因此,理解並活用Block也逐漸成為新一代iOS程式設計師應當做好的基本功。
沒有留言:
張貼留言