探尋Block的本質(2)—— 底層結構markdown
探尋Block的本質(3)—— 基礎類型的變量捕獲iphone
探尋Block的本質(6)—— __block的深刻分析post
首先咱們來看這麼一段代碼案例ui
*********************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
對象只要出了做用域就會被釋放,這一點是很好理解的。atom
上面的代碼加入block
,調整以下spa
*********************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
對象沒有被釋放。這裏有兩個注意點:3d
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()
這裏還須要注意的是,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
指針以下
爲了對比,咱們再分別看一下下面三種 場景分別是什麼狀況的:
block
-->弱指針__weak CLPerson *person
block
-->強指針CLPerson *person
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變量
的捕獲過程。