在前面我們已經講過對基本資料型別的變數捕獲Block學習②--block的變數捕獲,那麼對物件變數的捕獲是否和對基本資料型別一樣呢,我們來看下面程式碼
#import <Foundation/Foundation.h>
#import "RGPerson.h"
typedef void(^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
RGPerson *person = [[RGPerson alloc] init];
person.name = @"xxx";
block = ^{
NSLog(@"%@",person.name);
};
}
NSLog(@"________");
block ();
}
return 0;
}
// 列印結果:
2018-07-10 15:41:33.056744+0800 block-01[1813:115684] ________
2018-07-10 15:41:33.057089+0800 block-01[1813:115684] xxx
2018-07-10 15:41:33.057115+0800 block-01[1813:115684] -[RGPerson dealloc]
複製程式碼
person在作用域結束之後,person物件並沒有被釋放,person物件作為auto物件,會被block捕獲,但為什麼沒有被釋放呢,我們的猜想應該就是被block強引用了,我們通過C++原始碼來檢視一下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
RGPerson *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, RGPerson *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製程式碼
上述程式碼中,可以看到,我們的猜想確實如此,這只是在ARC的環境下,那我們要是切換到MRC環境下呢,畢竟ARC環境下,系統會幫我們做很多事情
#import <Foundation/Foundation.h>
#import "RGPerson.h"
typedef void(^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
RGPerson *person = [[RGPerson alloc] init];
person.name = @"xxx";
block = ^{
NSLog(@"%@",person.name);
};
[person release];
}
NSLog(@"________");
block ();
}
return 0;
}
//列印結果:
2018-07-10 15:51:28.042472+0800 block-01[1890:121165] -[RGPerson dealloc]
2018-07-10 15:51:28.042792+0800 block-01[1890:121165] ________
2018-07-10 15:51:28.042870+0800 block-01[1890:121165] xxx
複製程式碼
通過檢視列印結果,我們可以知道,在MRC環境下,person在作用域結束後,就被釋放了,我們對block進行一次copy呼叫,person沒有被釋放
前面的文字我們也提到過,對棧上面的block進行一次copy呼叫,會將block複製到堆上面,既然到了堆上面,那麼就相當於對block進行了一次retain操作,只要block不被銷燬,person物件也不會銷燬,也就是說,在棧上面,block不會對auto變數進行強引用
__weak
在ARC環境下,為程式碼加上一個__weak修飾符,再進行列印
#import <Foundation/Foundation.h>
#import "RGPerson.h"
typedef void(^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
RGPerson *person = [[RGPerson alloc] init];
person.name = @"xxx";
__weak RGPerson *weakP = person;
block = ^{
NSLog(@"%@",weakP.name);
};
}
NSLog(@"________");
block ();
}
return 0;
}
//列印結果
2018-07-10 16:10:08.824923+0800 block-01[1972:130602] -[RGPerson dealloc]
2018-07-10 16:10:08.825252+0800 block-01[1972:130602] ________
2018-07-10 16:10:08.825314+0800 block-01[1972:130602] (null)
複製程式碼
person物件在作用域結束後就被釋放了,為了能夠更加清晰的看到block內部的實現,我們將其轉換成C++,但是我們要使用另外的一個命令列,防止因為__weak而導致報錯
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
複製程式碼
轉換成功以後如下
然後我們把__weak修飾符去掉在進行轉換
我們可以看到,兩張圖中person使用不同的修飾符,而這兩個修飾符有起了什麼作用呢,我們繼續往下看
在瞭解block本質的時候,我們知道,在block的Desc當中,只有兩個引數,而現在卻又多出了兩個函式,這兩個函式是幹什麼的呢?
__main_block_copy_0
在函式 __main_block_desc_0的copy其實就是 __main_block_copy_0函式
__main_block_dispose_0
在函式 __main_block_desc_0的dispose其實就是 __main_block_dispose_0函式
上面的copy和dispose相當於OC程式碼中的retain和release,在上面的圖中我們可以發現,兩個函式都呼叫了_Block_object_assign這個函式,這個函式是有什麼用的呢?我們繼續往下看
_Block_object_assign
呼叫 _Block_object_assign函式,我們需要將person物件傳入,以及其他的引數。
__main_block_copy_0對_Block_object_assign呼叫
當對該函式進行呼叫時,該函式會根據__main_block_impl_0結構體內部,person物件的修飾符來進行處理,當使用的是__strong時,將會對person物件的引用計數加1,當為__weak時,引用計數不變
__main_block_dispose_0對_Block_object_assign呼叫
當對該函式進行呼叫,會對person物件進行釋放操作
總結
當block內部訪問了物件型別的auto變數時
如果block是在棧上面,將不會對auto變數產生強引用
如果block被拷貝到堆上,會呼叫block內部的copy函式,copy函式內部會呼叫_Block_object_assign函式,_Block_object_assign函式會根據auto變數的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用
如果block從堆上移除,會呼叫block內部的dispose函式,dispose函式內部會呼叫_Block_object_dispose函式,_Block_object_dispose函式會自動釋放引用的auto變數(release)