一、Block的類型
根據Block在內存中的位置分為三種類型NSGlobalBlock,NSStackBlock, NSMallocBlock。
二、Block的copy、retain、release操作
不同於NSObjec的copy、retain、release操作:
三、ARC與非ARC下的block
對於引用了外部變量的Block,如果沒有對他進行copy,他的作用域只會在聲明他的函數棧內(類型是__NSStackBlock__),如果想在非ARC下直接返回此類Block,Xcode會提示編譯錯誤的,如下圖:

(Xcode提示Returning block that lives on the local stack)
而在ARC環境下,上述代碼會編譯通過,因為ARC會自動加入copy操作。
比如可以在ARC下運行如下代碼:
//ARC MyBlock block = func(); NSLog(@"%d", block()); NSLog(@"%@", [block class]);
輸出:
123 __NSMallocBlock__
類型是__NSMallocBlock__,說明Block已經被copy到了堆中了。
即便把Block用strong修飾,系統也會把block copy到堆中。
例如:@property (nonatomic,strong)void (^SubmitBlock)(GoodsModel *goodsModel,int number);
打印一下block的類型為如下圖

當然其實在非ARC下,也可以使上面有錯誤的函數編譯通過。如下代碼:
typedef int(^MyBlock)();
MyBlock func()
{
int i = 123;
//非ARC下不要這樣!!!
MyBlock ret = ^{ return i; };
return ret;
}
我們把原來的返回值賦給一個變量,然後再返回這個變量,就可以編譯通過了。不過雖然編譯通過了,這個返回的Block作用域仍是在函數棧中的,因此一旦函數運行完畢後再使用這個Block很可能會引發BAD_ACCESS錯誤。
所以在非ARC下,必須把Block復制到堆中才可以在函數外使用Block,如下正確的代碼:
typedef int(^MyBlock)();
MyBlock func()
{
//非ARC
int i = 123;
return [^{ return i; } copy];
}
我們可以直接通過輸出變量的指針,就可以驗證Block被copy後,他所引用的變量被復制到了堆中的情況,如下代碼(非ARC下):
//非ARC
void func()
{
int a = 123;
__block int b = 123;
NSLog(@"%@", @"=== block copy前");
NSLog(@"&a = %p, &b = %p", &a, &b);
void(^block)() = ^{
NSLog(@"%@", @"=== Block");
NSLog(@"&a = %p, &b = %p", &a, &b);
NSLog(@"a = %d, b = %d", a, b = 456);
};
block = [block copy];
block();
NSLog(@"%@", @"=== block copy後");
NSLog(@"&a = %p, &b = %p", &a, &b);
NSLog(@"a = %d, b = %d", a, b);
[block release];
}
輸出:
=== block copy前 &a = 0x7fff5fbff8bc, &b = 0x7fff5fbff8b0 === Block &a = 0x100201048, &b = 0x100201068 a = 123, b = 456 === block copy後 &a = 0x7fff5fbff8bc, &b = 0x100201068 a = 123, b = 456
可以看到,在Block執行中,他所引用的變量a和b都被復制到了堆上。而被標記__block的變量事實上應該說是被移動到了堆上,因此,當Block執行後,函數棧內訪問b的地址會變成堆中的地址。而變量a,仍會指向函數棧內原有的變量a的空間。
四、So,Block屬性的聲明,首先需要用copy修飾符。Block默認存放在棧中,可能隨時被銷毀,需要作用域在堆中,所以只有copy後的Block才會在堆中,棧中的Block的生命周期是和棧綁定的。
五、循環引用的問題
循環引用是另一個使用Block時常見的問題。為什麼會循環引用?因為retain。
因為block在拷貝到堆上的時候,會retain其引用的外部變量,那麼如果block中如果引用了他的宿主對象,那很有可能引起循環引用,如:
self.myblock = ^{
[self doSomething];
};
下面是對ARC環境做的測試:
- (void)dealloc
{
NSLog(@"no cycle retain");
}
- (id)init
{
self = [super init];
if (self) {
#if TestCycleRetainCase1
//會循環引用
self.myblock = ^{
[self doSomething];
};
#elif TestCycleRetainCase2
//會循環引用
__block TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase3
//不會循環引用
__weak TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase4
//不會循環引用
__unsafe_unretained TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#endif
NSLog(@"myblock is %@", self.myblock);
}
return self;
}
- (void)doSomething
{
NSLog(@"do Something");
}
int main(int argc, char *argv[]) {
@autoreleasepool {
TestCycleRetain* obj = [[TestCycleRetain alloc] init];
obj = nil;
return 0;
}
}
經過上面的測試發現,在加了__weak和__unsafe_unretained的變量引入後,TestCycleRetain方法可以正常執行dealloc方法,而不轉換和用__block轉換的變量都會引起循環引用。
因此防止循環引用的方法如下: __weak TestCycleRetain *weakSelf = self;
所以,在ARC下,由於__block抓取的變量一樣會被Block retain,所以必須用弱引用才可以解決循環引用問題,iOS 5之後可以直接使用__weak,之前則只能使用__unsafe_unretained了,__unsafe_unretained缺點是指針釋放後自己不會置空。示例代碼:
//iOS 5之前可以用__unsafe_unretained
//__unsafe_unretained typeof(self) weakSelf = self;
__weak typeof(self) weakSelf = self;
self.myBlock = ^(int paramInt)
{
//使用weakSelf訪問self成員
[weakSelf anotherFunc];
};
在非ARC下,顯然無法使用弱引用,這裡就可以直接使用__block來修飾變量,它不會被Block所retain的,參考代碼:
//非ARC
__block typeof(self) weakSelf = self;
self.myBlock = ^(int paramInt)
{
//使用weakSelf訪問self成員
[weakSelf anotherFunc];
};
六、__weak 和 __block的區別
1. 循環引用時:有上文可知,__weak用在ARC環境時;__block僅可用在非ARC環境時(因為ARC環境下仍然會被retain)。
2.修改局部變量時:需要加__block,否則不能在block中修改局部變量。如下:
__block int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
multiplier ++;//這樣就可以了
return num * multiplier;
};