首先我們來看這么一段代碼案例
*********************CLPerson.h*********************
#import <Foundation/Foundation.h>
@interface CLPerson : NSObject
@property (nonatomic,assign) int age;
@end
*********************CLPerson.m*********************
#import "CLPerson.h"
@implementation CLPerson
-(void)dealloc {
NSLog(@"%s",__func__);
}
@end
*********************main.m*********************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
{//臨時作用域開始
CLPerson *person = [[CLPerson alloc] init];
person.age = 10;
}//臨時作用域結束
NSLog(@"-----------flag1");
}
return 0;
}
通過在打印標記
flag1
處斷點調試可看出,在臨時作用域里面的person
對象只要出了作用域就會被釋放,這一點是很好理解的。
上面的代碼加入block
,調整如下
*********************CLPerson.h*********************
#import <Foundation/Foundation.h>
@interface CLPerson : NSObject
@property (nonatomic,assign) int age;
@end
*********************CLPerson.m*********************
#import "CLPerson.h"
@implementation CLPerson
-(void)dealloc {
NSLog(@"%s",__func__);
}
@end
*********************main.m*********************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
typedef void(^CLBlock)(void);//???
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLBlock myBlock;//???
{//臨時作用域開始
CLPerson *person = [[CLPerson alloc] init];
person.age = 10;
myBlock = ^{//???
NSLog(@"---------%d",person.age);
};
}//臨時作用域結束
NSLog(@"-----------flag1");
}
return 0;
}
再次在打印標記flag1
處斷點調試運行一下
結果告訴我們,出了臨時作用域,
person
對象沒有被釋放。這里有兩個注意點:
- 由于現在是ARC環境,
myBlock
屬于強指針,因此在將block對象賦值給myBlock
指針的時候,編譯器會自動對block對象執行copy
操作,因此賦值完成后,myBlock
指向的是一個堆空間上的block對象副本。
順便,通過終端命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
,拿到編譯后的.cpp文件,我們先查看一下當前代碼編譯底層樣式,經整理簡化后如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
CLPerson *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, CLPerson *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
CLPerson *person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__p19yp82j0xd2m_1k8fpr77z40000gn_T_main_2cca58_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
CLBlock myBlock;
{
CLPerson * person = objc_msgSend(objc_msgSend(objc_getClass("CLPerson"),
sel_registerName("alloc")
),
sel_registerName("init")
);
objc_msgSend(person,
sel_registerName("setAge:"),
30
);
myBlock = objc_msgSend(&__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA,
person,
570425344),
sel_registerName("copy")
);
}
}
return 0;
}
以上是 【ARC環境-->堆上的block
-->強指針CLPerson *person
】所對應的運行結果以及底層實現。我們發現于之前捕獲一個基本類型的auto
變量所不同的是,當block捕捉對象類型的auto
變量的時候,__main_block_desc_0
結構體里面多了兩個彩蛋
- 函數指針
copy
,也就是__main_block_copy_0()
,內部調用了_Block_object_assign()
- 函數指針
dispose
,也就是__main_block_dispose_0()
,內部調用了_Block_object_dispose()
捕捉對象類型的auto變量時__main_block_desc_0的變化
這里還需要注意的是,ARC 下CLPerson *person
被認為是強指針,等價于_strong CLPerson *person
,而弱指針需要顯式地表示為__weak CLPerson *person
。通過終端命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m -o main.cpp
,可以看到block
的內捕獲到的person
指針如下
image
為了對比,我們再分別看一下下面三種 場景分別是什么情況的:
- ARC環境-->堆上的
block
-->弱指針__weak CLPerson *person
- ARC環境-->棧上的
block
-->強指針CLPerson *person
- ARC環境-->棧上的
block
-->弱指針__weak CLPerson *person
【ARC環境-->堆上的block
-->弱指針__weak CLPerson *person
】案例如下
***********************main.m*************************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
typedef void(^CLBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLBlock myBlock;
{//臨時作用域開始
__weak CLPerson * person = [[CLPerson alloc] init];
person.age = 30;
myBlock = ^{
NSLog(@"---------%d",person.age);
} ;
}//臨時作用域結束
NSLog(@"-------------");
}
NSLog(@"------main autoreleasepool end-------");
return 0;
}
block的底層結構如下
運行結果顯示堆上的block使用弱指針
__weak CLPerson *person
,沒有影響person
所指向對象的生命周期,出了臨時作用域的之后就被釋放了。
【ARC環境-->棧上的block
-->強指針CLPerson *person
】
***********************main.m*************************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
typedef void(^CLBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLBlock myBlock;
{//臨時作用域開始
CLPerson * person = [[CLPerson alloc] init];
person.age = 30;
^{
NSLog(@"---------%d",person.age);
} ;
}//臨時作用域結束
NSLog(@"-------------");
}
NSLog(@"------main autoreleasepool end-------");
return 0;
}
block底層結構如下
運行結果顯示棧上的block使用強指針CLPerson *person
,沒有影響person
所指向對象的生命周期,出了臨時作用域的之后就被釋放了。
【ARC環境-->棧上的block
-->弱指針__weak CLPerson *person
】
***********************main.m*************************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
typedef void(^CLBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLBlock myBlock;
{//臨時作用域開始
__weak CLPerson * person = [[CLPerson alloc] init];
person.age = 30;
^{
NSLog(@"---------%d",person.age);
} ;
}//臨時作用域結束
NSLog(@"-------------");
}
NSLog(@"------main autoreleasepool end-------");
return 0;
}
block底層結構為
運行結果顯示棧上的block使用弱指針__weak CLPerson *person
,沒有影響person
所指向對象的生命周期,出了臨時作用域的之后就被釋放了。
從上面的四種情況的對比看出,只有堆上的block
使用強指針CLPerson *person
的時候,才會影響該指針所指對象的生命周期,這是怎么一回事呢?
【這個時候就要回到我們上面發現的那兩個彩蛋了】也就是__main_block_desc_0
中的函數指針copy
和dispose
。實際上這兩個函數指針分別是在block從棧空間拷貝到堆空間的時候,以及堆空間的block被釋放的時候使用的。
當block
從棧拷貝到堆時,系統會通過block
中的函數指針copy
調用函數__xxx_block_copy_x
__xxx_block_copy_x(struct __main_block_impl_0*dst,
struct __main_block_impl_0*src
);
注意這個函數的兩個參數,
- 第一個
dst
代表拷貝之前棧空間的block, - 第二個
src
代表拷貝之后堆空間上的block。
這個函數里面調用的是_Block_object_assign
函數
_Block_object_assign((void*)&dst->person,
(void*)src->person,
3/*BLOCK_FIELD_IS_OBJECT*/
);
這個函數的作用是把dst
(棧block)內部所捕獲的那個person
對象(指針)的值賦值(assign)給src
(堆block)內的person
指針。
如果是ARC環境,會根據person
的強弱性(也就是之前的修飾詞 __weak
和 __strong
)來決定src
(堆block)是否持有對象。如果person
是被__strong
修飾,就會通過 [person retain]
,使得person
所指向的對象的引用計數+1,這樣,src
(堆block)就持有了(也就是強引用)person
所指向的對象。
當堆上的block
即將被釋放的時候,系統會通過block
中的函數指針dispose
調用函數__xxx_block_dispose_x
__main_block_dispose_0(struct __main_block_impl_0*src);
很明顯其中的參數src
就是堆上的block
,該函數內部調用了_Block_object_dispose
函數
_Block_object_dispose((void*)src->person,
3/*BLOCK_FIELD_IS_OBJECT*/
);
ARC下,該函數的作用是,根絕src
(堆block)內部的person
指針的強弱性(__weak/__strong
),決定如何處理person
所指向的對象,如果是__strong
,說明src
持有了person
所指向的對象(也就是強引用),因為此時src即將被釋放,所以需要放開持有,也即是調用一下[person release]
,使得person
所指對象的引用計數-1。
為什么上面我都強調了ARC環境下 呢,因為MRC里面是沒有__weak
和__strong
這兩個東西的。ARC就是根據我們代碼中的__weak/__strong
標記,來自動進行一些內存管理相關的處理。相信上面的分析應該能讓你有所領悟。
如果回到MRC,其實內存管理本質原則沒有變化,無非就是retain
和release
操作,我個人認為過程會相對ARC來說更簡單一些。
首先,內部使用了auto變量(基礎類型/對象類型)的block
是在棧空間上的,它不會對對象類型的auto變量
進行retain
操作,也就是不會持有該對象。
當我們對一個棧block調用copy
方法進行拷貝操作的時候,會跟ARC一樣以相同的方式最終調用_Block_object_assign
函數,只不過此時會直接對堆block上 所捕獲的對象進行調用retain
方法,進行持有,不會進行__weak/__strong
的判斷,因為MRC里面根本沒有這兩個小兄弟。
當一個堆上的block即將釋放的時候,也會最終調用_Block_object_dispose
函數,對該block
所捕獲的對象調用release
方法,這樣堆上的block就放開了對該對象的持有(強引用)。
最后,在通過幾張圖示,來說明一下【block對于對象類型的auto變量的捕獲原理】
以上就是block對于對象類型的auto變量
的捕獲過程。