從上些章節block-變量的捕獲(caputer)中,詳細說了基本類型的auto變量的捕獲,現在來了解下,對象類型的auto變量是怎樣捕獲和底層結構是如何的。
block自動copy的情況
在ARC環境下,編譯器會根據情況自動將棧上的block複製到堆上,比如以下情況
- block
作爲函數返回值時
- block
賦值給__strong
指針時
- block
作爲Cocoa API
中方法名含有usingBlock
的方法參數時
- block
作爲GCD API
的方法參數時
#import <Foundation/Foundation.h>
#import "RMPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
RMPerson *person = [[RMPerson alloc] init];
person.age = 20;
void (^block)(void) = ^ {
NSLog(@"age is %d",person.age);
};
[person release];
NSLog(@"-----------");
}
return 0;
}
----------------- RMPerson.h -----------------
#import <Foundation/Foundation.h>
@interface RMPerson : NSObject
@property (nonatomic, assign) int age;
@end
----------------- RMPerson.m -----------------
#import "RMPerson.h"
@implementation RMPerson
- (void)dealloc {
[super delloc];
NSLog(@"RMPerson-delloc");
}
@end
// MRC 環境 控制檯輸出
2018-07-03 10:34:39.523223+0800 __block的本質[20912:1898201] RMPerson-delloc
2018-07-03 10:36:17.021712+0800 __block的本質[20912:1898201] -----------
Program ended with exit code: 0
留意上面代碼,是在MRC的環境下的代碼,當NSLog(@"-----------");
打印前了,RMPerson就釋放了,什麼原因呢?雖然block訪問的是對象類型的auto變量
,但還是訪問了auto變量,所以block是屬於NSStackBlock,是存在棧空間的,block運行完就會釋放,它自己都不知道自己能存活多久,所以是不會作強持有RMPerson的操作。(結論一:如果block是在棧上,將不會對auto變量產生強引用)
#import <Foundation/Foundation.h>
#import "RMPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
RMPerson *person = [[RMPerson alloc] init];
person.age = 20;
void (^block)(void) = [^ {
NSLog(@"age is %d",person.age);
} copy]; // copy操作,從棧中複製到堆中
[person release];
NSLog(@"-----------");
}
return 0;
}
----------------- RMPerson.h -----------------
#import <Foundation/Foundation.h>
@interface RMPerson : NSObject
@property (nonatomic, assign) int age;
@end
----------------- RMPerson.m -----------------
#import "RMPerson.h"
@implementation RMPerson
- (void)dealloc {
[super delloc];
NSLog(@"RMPerson-delloc");
}
@end
// MRC 環境 控制檯輸出
2018-07-03 10:49:35.425698+0800 __block的本質[21020:1916302] -----------
Program ended with exit code: 0
從上面的控制檯輸出可以看出,即使程序結束了,RMPerson都沒有釋放,這是爲什麼呢? 因爲block做了copy操作,從棧中拷貝到了堆中,此時block強引用了RMPerson,所以保住了RMPerson。
下面我們來看一下,底層c++代碼是如何堆中的block是如何保住RMPerson的。
從上面源碼分析,
如果block被拷貝到對上時
- 1.會調用block內部的copy函數
- 2.copy函數內部會調用_Block_object_assign函數
- 3._Block_object_assign函數會根據auto變量的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用。
如果block從堆上移除
- 1.會調用block內部的dispose函數
- 2.dispose函數內部會調用_Block_object_dispose函數
- 3._Block_object_dispose函數會自動釋放引用的auto變量(release)
總結:
當block內部訪問了對象類型的auto變量時
1.如果block是在棧上,將不會對auto變量產生強引用
2.如果block被拷貝到堆上
- 會調用block內部的copy函數
- copy函數內部會調用_Block_object_assign函數
- _Block_object_assign函數會根據auto變量的修飾符(__strong、__weak、* __unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用
3.如果block從堆上移除
- 會調用block內部的dispose函數
- dispose函數內部會調用_Block_object_dispose函數
- _Block_object_dispose函數會自動釋放引用的auto變量(release)