靜態(tài)變量和全局變量為什么不用加__block都可以修改值
1、靜態(tài)變量和全局變量在加和不加__block都會直接引用變量地址扣泊。也就意味著可以修改變量的值近范。在沒有加__block關(guān)鍵字的情況下。
2延蟹、常量變量(NSString *a=@"hello"; a為變量,@“hello”為常量评矩。)
不加__block類型,block會直接取常量值(淺拷貝)阱飘。
加__block類型斥杜,block會去引用變量的地址。(如:a變量,a = @"abc".可以任意修改a 指向的內(nèi)容沥匈。)
如果不加__block 直接在block內(nèi)部修改變量 ,會編譯報(bào)錯蔗喂。block內(nèi)部改變量是只讀的。
Block的分類
- block主要分為三類:
① 全局block:_NSConcreteGlobalBlock
高帖;存儲在全局內(nèi)存中缰儿,相當(dāng)于單例。
② 棧block:_NSConcreteStackBlock
散址;存儲在棧內(nèi)存中乖阵,超出其作用域則馬上被銷毀。
③ 堆block:_NSConcreteMallocBlock
爪飘;存儲在堆內(nèi)存中义起,是一個(gè)帶引用計(jì)數(shù)的對象,需要
自行管理其內(nèi)存师崎。
auto變量:
棧有兩種分配方式:靜態(tài)分配和動態(tài)分配默终。靜態(tài)分配是編譯器完成的,比如局部變量的分配犁罩。動態(tài)分配是有alloc函數(shù)進(jìn)行分配的齐蔽,但是棧的動態(tài)分配和堆是不同的,他的動態(tài)分配由編譯器進(jìn)行釋放床估,無需我們手工實(shí)現(xiàn)含滴。
即:
block的類型 | 環(huán)境 |
---|---|
__NSGlobalBlock__ |
沒有訪問 auto 變量 |
__NSStackBlock__ |
訪問了 auto 變量 |
__NSMallocBlock__ |
NSStackBlock 調(diào)用了 copy |
這三種block各自的存儲區(qū)域如下圖:
簡而言之,存儲在棧中的block
就是棧塊丐巫,存儲在堆區(qū)的就是堆塊谈况,既不在棧區(qū)也不在堆區(qū)的就是全局塊
- 當(dāng)我們遇到一個(gè)
block
勺美,怎么去判定這個(gè)block的存儲位置呢?
外部變量:
(1)block
不訪問外部變量(包括棧和堆中的變量)
此時(shí)block
既不在棧中碑韵,也不在堆中赡茸,在代碼段中。ARC
和MRC
下都是如此祝闻。
此時(shí)為全局block:_NSConcreteGlobalBlock
void(^block)(void) = ^{
};
NSLog(@"%@", block);
/*輸出結(jié)果為*/
<__NSGlobalBlock__: 0x100004030>
(2)block訪問外部變量
MRC
環(huán)境下:訪問外部變量的block
默認(rèn)是存儲在棧
中的占卧。
ARC
環(huán)境下:訪問外部變量的block
默認(rèn)是存儲在堆
中的(實(shí)際是放在棧
區(qū),然后ARC
情況下又自動拷貝到堆
區(qū))联喘,自動釋放华蜒。
- MRC 環(huán)境:
int a = 10;
void(^block)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"%@", block);
/*輸出結(jié)果為*/
<__NSStackBlock__: 0x7ffeefbff3e8>
- ARC 環(huán)境下
int a = 10;
void(^block)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"%@", block);
/*輸出結(jié)果為*/
<__NSMallocBlock__: 0x1040508b0>
- 在ARC環(huán)境下我們怎么獲取
棧block
呢?
我們可以這樣做:
int a = 10;
void(^ __weak block)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"%@", block);
/*輸出結(jié)果為*/
<__NSStackBlock__: 0x7ffeefbff3e8>
此時(shí)我們通過__weak
不進(jìn)行強(qiáng)持有豁遭,block
就還是棧區(qū)的block叭喜。
- ARC環(huán)境下,訪問外部變量的
block
為什么要自動從棧區(qū)拷貝到堆區(qū)呢堤框?
因?yàn)椋簵I系?code>block域滥,如果其所屬的變量作用域結(jié)束,該block
就會被廢棄蜈抓,如同一般的自動變量启绰。當(dāng)然,block
中的__block
變量也同時(shí)會被廢棄沟使。
為了解決棧塊在其變量作用域結(jié)束之后被廢棄(釋放)的問題委可,我們需要把block
復(fù)制到堆中,延長其生命周期腊嗡。開啟ARC
時(shí)着倾,大多數(shù)情況下編譯器會恰當(dāng)?shù)倪M(jìn)行判斷是否有必要將block
從棧復(fù)制到堆,如果有燕少,自動生成將block
從棧復(fù)制到堆的代碼卡者。block
的復(fù)制操作執(zhí)行的是Copy
實(shí)例方法。block
只要調(diào)用了Copy
方法客们,棧塊就會變成堆塊崇决。
eg:
typedef int(^myblock)(int);
myblock func(int a) {
return ^(int b) {
return a * b;
};
}
上面的代碼中,函數(shù)返回的block
是配置在棧上的底挫,所以返回返回函數(shù)調(diào)用方法時(shí)恒傻,block
變量作用域就被釋放了,block
也會被釋放建邓。但是盈厘,在ARC
環(huán)境下是有效的,這種情況編譯器會自動完成復(fù)制官边。
在非ARC
情況下則需要開發(fā)者調(diào)用Copy
方法手動復(fù)制沸手。
block的類型 | 副本源的配置存儲區(qū)域 | 復(fù)制效果 |
---|---|---|
_NSConcreteGlobalBlock |
程序的數(shù)據(jù)區(qū)域 | 什么也不做 |
_NSConcreteStackBlock |
棧區(qū) | 從棧區(qū)復(fù)制到堆區(qū) |
_NSConcreteMallocBlock |
堆區(qū) | 引用計(jì)數(shù)增加 |
根據(jù)表格我們知道外遇,block
在堆區(qū)Copy會造成引用計(jì)數(shù)增加,這與其它OC對象是一樣的契吉。雖然block
在棧中也是以對象的身份存在臀规,但是棧區(qū)沒有引用計(jì)數(shù),因?yàn)椴恍枰ひ覀兌贾罈^(qū)的內(nèi)存由編譯器自動分配釋放。
三玩徊、block 底層分析
int a = 10;
void(^ block)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"%@", block);
使用 clang
將OC
代碼轉(zhuǎn)換成C++
文件租悄,查看block
的方法。
- 在命令行輸入下面的指令(XXX.m就是要編譯的文件恩袱,需在當(dāng)前文件夾下面執(zhí)行)
clang -rewrite-objc XXX.m
執(zhí)行網(wǎng)上面的指令之后泣棋,當(dāng)前文件夾中會多一個(gè)
XXX.cpp
的文件。此時(shí)在命令行輸入open XXX.cpp
或者 直接打開文件打開
XXX.cpp
文件畔塔,在文件底部我們可以看到main
函數(shù)被編譯之后的樣式:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
void(* block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__pbdfr2t16xx3pkwg63c2y5m80000gn_T_main_c75271_mi_1, block);
}
return 0;
}
我們從main
函數(shù)中提取一下block
void(* block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
/*簡化一下潭辈,去除強(qiáng)制轉(zhuǎn)換*/
void(* block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a); ///構(gòu)造函數(shù)
可以看到構(gòu)造函數(shù)名為__main_block_impl_0
下面我們再尋找一下__main_block_impl_0
:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 可以看到
__main_block_impl_0
是一個(gè)結(jié)構(gòu)體。同時(shí)我們也可以說block
是一個(gè)__main_block_impl_0
類型的對象澈吨,這也是為什么block
能夠%@
打印的原因
1把敢、block自動捕獲外部變量
- block自動捕獲的外部變量,在block的函數(shù)體內(nèi)是不允許被修改的谅辣。
① 通過上面的代碼我們可以看到__main_block_impl_0
函數(shù)的定義:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; ///編譯器自動生成的名字相同的變量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy 值拷貝
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__pbdfr2t16xx3pkwg63c2y5m80000gn_T_main_ab7cb4_mi_0, a);
}
可以看到修赞,編譯器會自動生成一個(gè)同名的變量。
__main_block_func_0
中a
是值拷貝桑阶。
因此柏副,在block
內(nèi)存會生成一個(gè),內(nèi)容一樣的同名變量蚣录,此時(shí)如果在函數(shù)體內(nèi)進(jìn)行a++
的操作割择,則編譯器就不清楚該去修改哪個(gè)變量。所以block
自動捕獲的變量萎河,在函數(shù)體內(nèi)部是不允許修改的荔泳。
- 那么我們要修改外部變量要怎么辦呢?
1公壤、__block
修飾外部變量换可。
2、將變量定義成全局變量
3厦幅、將變量用參數(shù)的形式沾鳄,傳入block
里面。
第2種和第3種方式确憨,想必大家都非常的熟悉译荞,在這里就不再贅述瓤的。下面我們來看一下第1種方式,底層究竟做了些什么吞歼。
__block 原理
- 現(xiàn)在我們對
a
進(jìn)行__block
編譯圈膏,之后我們就可以在block
內(nèi)部對a
進(jìn)行修改。
__block int a = 10;
void(^ block)(void) = ^{
a++;
NSLog(@"%d", a);
};
block();
下面我們再通過clang
來觀察一下篙骡,底層代碼有了什么變化稽坤。
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref 指針拷貝
/// 等同于外界的 a++
(a->__forwarding->a)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__pbdfr2t16xx3pkwg63c2y5m80000gn_T_main_b21337_mi_0, (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
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;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void(* block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
- 首先我們看到,在
main
函數(shù)里面糯俗,此時(shí)a
變成了成了一個(gè)__Block_byref_a_0
類型的對象尿褪。 - 同時(shí)在
__main_block_func_0
函數(shù)中,由之前的值拷貝(bound by copy
) 變成了現(xiàn)在的指針拷貝(bound by ref
)
*在main
函數(shù)中傳入的a是一個(gè)對象得湘,同時(shí)在__main_block_func_0
函數(shù)內(nèi)部杖玲,對a
進(jìn)行指針拷貝;則此時(shí)創(chuàng)建的對象a
和傳入的對象a
指向同一片內(nèi)存空間淘正。
總結(jié):
__block
修飾外界變量的時(shí)候:
1摆马、外界變量會生成__Block_byref_a_0
結(jié)構(gòu)體
2、結(jié)構(gòu)體用來保存原始變量的指針
和值
(可以在上面編譯后的代碼中找到)
3鸿吆、將變量生成的結(jié)構(gòu)體對象的指針地址
傳遞給block
,然后在block
內(nèi)部就可以對外界變量
進(jìn)行修改了囤采。
接下來,在給大家看一個(gè)東西:
- 在上面的C++代碼中惩淳,
__main_block_func_0
函數(shù)中斑唬,大家會注意到執(zhí)行a++
的是這段代碼(a->__forwarding->a)++
;,那么這個(gè)__forwarding
又是什么呢黎泣?
接下來我們先看一下__Block_byref_a_0
結(jié)構(gòu)體長什么樣子:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
可以看到恕刘,__forwarding
是一個(gè)指向自己本身的指針(自身為結(jié)構(gòu)體)。
那么抒倚,在Copy
操作之后褐着,既然__block
變量也被Copy
到堆區(qū)上了,那么訪問該變量是訪問棧上的還是堆上的呢托呕?這個(gè)時(shí)候我們就要來看一下含蓉,在Copy
過程中__forwarding
的變化了:
可以看到,通過__forwarding
项郊,無論是在block
中馅扣,還是block
外訪問__block
變量,也不管該變量是在棧上或是在堆上着降,都能順利的訪問同一個(gè)__block
變量差油。
注意:這里與上面的結(jié)論并不矛盾。大家要主要到局部變量a
被__block
修飾之后,會變成__Block_byref_a_0
結(jié)構(gòu)體對象蓄喇。所以無論是在棧區(qū)還是在堆區(qū)发侵,只要__forwarding
指向的地址一樣,那么就可以在block
內(nèi)部修改外界變量妆偏。這里大家要仔細(xì)觀察一下__Block_byref_a_0
結(jié)構(gòu)體
Block對局部變量的修改特殊情況如:局部變量為NSMutableArray類型刃鳄,在block初始化了以后,對NSMutableArray這些add或者remove操作钱骂,都會同步到Block里邊的
-(void )test3
{
NSString *_person2=@"person2";
NSMutableArray *_listTest=[[NSMutableArray alloc]init];
//初始值
NSLog(@"init _person2:%@,%p",_person2,_person2);
NSLog(@"init _listTest:%@,%p",_listTest,_listTest);
void (^myBlock)(int) = ^(int num) {
//block內(nèi)賦值
// _weakPerson2=@"person22";
NSLog(@"excuteing _person2:%@,%p",_person2,_person2);
NSLog(@"excuteing _listTest:%@,%p",_listTest,_listTest);
};
//修改前賦值
_person2=@"person22";
[_listTest addObject:@"1212"];
NSLog(@"excutebefore _person2:%@,%p",_person2,_person2);
NSLog(@"excutebefore _listTest:%@,%p",_listTest,_listTest);
myBlock(1);
//block執(zhí)行后
NSLog(@"excuteafter _person2:%@,%p",_person2,_person2);
NSLog(@"excuteafter _listTest:%@,%p",_listTest,_listTest);
}
輸出結(jié)果
2014-07-29 11:05:29.460 Test[2540:60b] init _person2:person2,0xb18ec
2014-07-29 11:05:29.463 Test[2540:60b] init _listTest:(
),0x17d98560
2014-07-29 11:05:29.464 Test[2540:60b] excutebefore _person2:person22,0xb193c
2014-07-29 11:05:29.465 Test[2540:60b] excutebefore _listTest:(
1212
),0x17d98560
2014-07-29 11:05:29.467 Test[2540:60b] excuteing _person2:person2,0xb18ec
2014-07-29 11:05:29.468 Test[2540:60b] excuteing _listTest:(
1212
),0x17d98560
2014-07-29 11:05:29.470 Test[2540:60b] excuteafter _person2:person22,0xb193c
2014-07-29 11:05:29.471 Test[2540:60b] excuteafter _listTest:(
1212
),0x17d98560
從日志可以看出:block內(nèi)部對于可變叔锐、不可變的變量都無法修改,而且
1.在block初始化后對于NSString 變量 _person2 的修改见秽,并沒有同步到block內(nèi)部掌腰,因?yàn)檫@時(shí)block外部的變量_person2指針重新指向另外一塊內(nèi)存
2.在block初始化后對于NSMutableArray變量 _listTest 的修改,同步到block內(nèi)部张吉,因?yàn)檫@時(shí)block外部的變量 _listTest 指針指向的內(nèi)存地址沒有變,只是對這塊內(nèi)存的值進(jìn)行了操作催植。
我們可以借助 clang -rewrite-objc 轉(zhuǎn)換.c文件得到.cpp文件肮蛹,也可以轉(zhuǎn)換.m也可以得到cpp文件(可能會有些報(bào)錯)
以下是部分轉(zhuǎn)換后的代碼
//這里就是block對象的結(jié)構(gòu)
//imp:函數(shù)指針對象,F(xiàn)uncPtr指向具體block實(shí)現(xiàn)的函數(shù)
//_person2:截獲的變量
//isa创南、flags伦忠、funcptr、desc后面會說道稿辙。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __KDBlockTest__test3_block_impl_0 {
struct __block_impl impl;
struct __KDBlockTest__test3_block_desc_0* Desc;
NSString *_person2;
NSMutableArray *_listTest;
__KDBlockTest__test3_block_impl_0(void *fp, struct __KDBlockTest__test3_block_desc_0 *desc, NSString *__person2, NSMutableArray *__listTest, int flags=0) : _person2(__person2), _listTest(__listTest) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block實(shí)現(xiàn)的函數(shù)
static void __KDBlockTest__test3_block_func_0(struct __KDBlockTest__test3_block_impl_0 *__cself, int num) {
NSString *_person2 = __cself->_person2; // bound by copy
NSMutableArray *_listTest = __cself->_listTest; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_4,_person2,_person2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_5,_listTest,_listTest);
}
//block對象的描述信息(大小等等)
static struct __main1_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main1_block_desc_0_DATA = { 0, sizeof(struct __main1_block_impl_0)};
//這是objc測試函數(shù)test
static void _I_KDBlockTest_test3(KDBlockTest * self, SEL _cmd) {
NSString *_person2=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_1;
NSMutableArray *_listTest=((id (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_2,_person2,_person2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_3,_listTest,_listTest);
void (*myBlock)(int) = (void (*)(int))&__KDBlockTest__test3_block_impl_0((void *)__KDBlockTest__test3_block_func_0, &__KDBlockTest__test3_block_desc_0_DATA, _person2, _listTest, 570425344);
_person2=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_6;
((void (*)(id, SEL, id))(void *)objc_msgSend)((id)_listTest, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_7);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_8,_person2,_person2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_9,_listTest,_listTest);
((void (*)(__block_impl *, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 1);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_10,_person2,_person2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_11,_listTest,_listTest);
}
簡單分析block截獲變量:
1).block初始化
void (*myBlock)(int) = (void (*)(int))&__KDBlockTest__test3_block_impl_0((void *)__KDBlockTest__test3_block_func_0, &__KDBlockTest__test3_block_desc_0_DATA, _person2, _listTest, 570425344);
傳入了參數(shù):函數(shù)指針昆码、block描述、外部變量 _person2 和 _listTest邻储,這時(shí)候在block內(nèi)部對 _person2赋咽、_listTest 進(jìn)行了引用
: _person2(__person2), _listTest(__listTest)
1.在block初始化后,我們對 _person2 做了修改吨娜,重新指向了 0xb193c 這塊內(nèi)存脓匿,但是不會影響block結(jié)構(gòu)體成員_person2,因?yàn)槌蓡T _person2 指向的是 0xb18ec。
2.向 _listTest 數(shù)組中添加了一個(gè)元素宦赠,并沒有改變它的內(nèi)存地址陪毡,依然還是 0x17d98560
_person2=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_6;
((void (*)(id, SEL, id))(void *)objc_msgSend)((id)_listTest, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_7);
2).執(zhí)行block
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
其實(shí)還是調(diào)用了block對象里的函數(shù)對象(_block_imp1)的函數(shù)指針(FuncPtr) 所指向的函數(shù)__main1_block_func_0,并把block自己作為參數(shù)傳遞進(jìn)去勾扭。
static void __KDBlockTest__test3_block_func_0(struct __KDBlockTest__test3_block_impl_0 *__cself, int num) {
NSString *_person2 = __cself->_person2; // bound by copy
NSMutableArray *_listTest = __cself->_listTest; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_4,_person2,_person2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_5,_listTest,_listTest);
}
總結(jié):對于局部變量毡琉,變量不加__block修飾符,在block內(nèi)部是無法修改變量的值妙色。而且
- 對值類型的修改桅滋,如果block初始化后,無法同步到block內(nèi)部
- 對于引用類型的修改身辨,如果block初始化后虱歪,修改指針指向蜂绎,即指向另外一塊內(nèi)存,這樣也是無法同步到block內(nèi)部
- 對于引用類型的修改笋鄙,如果block初始化后师枣,對指針指向的內(nèi)存進(jìn)行修改,即NSMutableArray add 萧落、remove操作践美,這樣是可以用同步到block內(nèi)部,但block內(nèi)部同樣無法修改找岖。
Block對成員變量
結(jié)論:
對于成員變量陨倡,結(jié)果卻不一樣,加了__block和不加__block修飾符效果都是一樣的许布,而且不用區(qū)分是引用類型和值類型兴革,block初始化后,對于block內(nèi)部引用的變量的修改蜜唾,也能同步到block內(nèi)部,并且在block內(nèi)部可以修改成員變量的值杂曲。
Demo:
聲明兩個(gè)變量:_person2、_person3
@interface KDBlockTest()
{
NSString *_person2;
__block NSString *_person3;
}
添加測試方法袁余,輸出變量的值擎勘、地址、指針地址
-(void )test3
{
_person2=@"person2";
_person3=@"person3";
//初始值
NSLog(@"init _person2:%@,%p",_person2,_person2);
NSLog(@"init _person3:%@,%p",_person3,_person3);
void (^myBlock)(int) = ^(int num) {
//block內(nèi)賦值
_person3=@"person33";
NSLog(@"excuteing _person2:%@,%p",_person2,_person2);
NSLog(@"excuteing _person3:%@,%p",_person3,_person3);
};
//修改前賦值
_person2=@"person22";
NSLog(@"excutebefore _person2:%@,%p",_person2,_person2);
NSLog(@"excutebefore _person3:%@,%p",_person3,_person3);
myBlock(1);
//block執(zhí)行后
NSLog(@"excuteafter _person2:%@,%p",_person2,_person2);
NSLog(@"excuteafter _person3:%@,%p",_person3,_person3);
}
執(zhí)行結(jié)果如下:
2014-07-29 12:06:11.526 Test[2575:60b] init _person2:person2,0x10790c
2014-07-29 12:06:11.529 Test[2575:60b] init _person3:person3,0x10791c
2014-07-29 12:06:11.530 Test[2575:60b] excutebefore _person2:person22,0x10797c
2014-07-29 12:06:11.531 Test[2575:60b] excutebefore _person3:person3,0x10791c
2014-07-29 12:06:11.532 Test[2575:60b] excuteing _person2:person22,0x10797c
2014-07-29 12:06:11.534 Test[2575:60b] excuteing _person3:person33,0x10794c
2014-07-29 12:06:11.535 Test[2575:60b] excuteafter _person2:person22,0x10797c
2014-07-29 12:06:11.536 Test[2575:60b] excuteafter _person3:person33,0x10794c
從日志可以看出颖榜,
- block內(nèi)部修改了成員變量_person3(
沒有用__block修飾符
)棚饵,并且同步到block外部,修改前和修改后地址是一樣的掩完。 - block初始化后噪漾,執(zhí)行前,修改成員變量_person2的值且蓬,可以同步到block內(nèi)部(
沒有用__block修飾符
)怪与,修改前和修改后地址是一樣的。
我們來看一下clang轉(zhuǎn)換后的代碼就會知道原因了
struct __KDBlockTest__test3_block_impl_0 {
struct __block_impl impl;
struct __KDBlockTest__test3_block_desc_0* Desc;
KDBlockTest *self;
__KDBlockTest__test3_block_impl_0(void *fp, struct __KDBlockTest__test3_block_desc_0 *desc, KDBlockTest *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
對于局部變量缅疟,block結(jié)構(gòu)體里對應(yīng)一個(gè)變量分别,都會有一個(gè)成員。
對于成員變量存淫,block結(jié)構(gòu)體里只會有一個(gè)成員變量耘斩,即 KDBlockTest *self,不管你是否用__block修飾了桅咆,此時(shí)對self產(chǎn)生了強(qiáng)引用
void (*myBlock)(int) = (void (*)(int))&__KDBlockTest__test3_block_impl_0((void *)__KDBlockTest__test3_block_func_0, &__KDBlockTest__test3_block_desc_0_DATA, self, 570425344);
在初始化的時(shí)候括授,把self傳到block結(jié)構(gòu)體構(gòu)造函數(shù)里,block對象對self產(chǎn)生了引用,此時(shí)我們對成員變量進(jìn)行修改
_person2=@"person22";
_person3=@"person33";
轉(zhuǎn)換后代碼
(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person2))=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_8;
這段代碼大致是修改self的objc變量荚虚。下面開始執(zhí)行block薛夜,即調(diào)用對應(yīng)的函數(shù)指針。
((void (*)(__block_impl *, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 1);
static void __KDBlockTest__test3_block_func_0(struct __KDBlockTest__test3_block_impl_0 *__cself, int num) {
KDBlockTest *self = __cself->self; // bound by copy
(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person3))=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_5;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_6,(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person2)),(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person2)));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_7,(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person3)),(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person3)));
}
函數(shù)實(shí)現(xiàn)里通過引用block結(jié)構(gòu)體的成員self版述,再引用到對應(yīng)的objc變量_person2和_person3梯澜。
小結(jié):
- 對于一個(gè)、多個(gè)成員變量渴析,不管是否用__block修飾(
用不用都沒任何影響
)晚伙,block結(jié)構(gòu)體會生成一個(gè)成員 :self,并且會引用成員變量所屬的對象實(shí)例 self。 - 對于成員變量的修改都是通過對象self指針引用來實(shí)現(xiàn)的俭茧。
- block內(nèi)部對于成員變量的訪問也是通過block結(jié)構(gòu)體對象的成員self 指針引用來實(shí)現(xiàn)的咆疗。