315,Block由淺入深(5):三種類型的Block(面試點(diǎn):1.globalBlock:沒有訪問auto(局部)變量,存儲在.data 區(qū)域淑趾,2.棧block:有訪問auto(局部)變量阳仔,存...

靜態(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ū)域如下圖:


image.png

簡而言之,存儲在棧中的block就是棧塊丐巫,存儲在堆區(qū)的就是堆塊谈况,既不在棧區(qū)也不在堆區(qū)的就是全局塊

  • 當(dāng)我們遇到一個(gè)block勺美,怎么去判定這個(gè)block的存儲位置呢?

外部變量

(1)block不訪問外部變量(包括棧和堆中的變量)
此時(shí)block既不在棧中碑韵,也不在堆中赡茸,在代碼段中。ARCMRC下都是如此祝闻。
此時(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í)會被廢棄沟使。
image.png

為了解決棧塊在其變量作用域結(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方法客们,棧塊就會變成堆塊崇决。

image.png

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);

使用 clangOC代碼轉(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_0a是值拷貝桑阶。
因此柏副,在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的變化了:

image.png

可以看到,通過__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)部是無法修改變量的值妙色。而且

  1. 對值類型的修改桅滋,如果block初始化后,無法同步到block內(nèi)部
  2. 對于引用類型的修改身辨,如果block初始化后虱歪,修改指針指向蜂绎,即指向另外一塊內(nèi)存,這樣也是無法同步到block內(nèi)部
  3. 對于引用類型的修改笋鄙,如果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

從日志可以看出颖榜,

  1. block內(nèi)部修改了成員變量_person3(沒有用__block修飾符)棚饵,并且同步到block外部,修改前和修改后地址是一樣的掩完。
  2. 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é):

  1. 對于一個(gè)、多個(gè)成員變量渴析,不管是否用__block修飾(用不用都沒任何影響)晚伙,block結(jié)構(gòu)體會生成一個(gè)成員 :self,并且會引用成員變量所屬的對象實(shí)例 self。
  2. 對于成員變量的修改都是通過對象self指針引用來實(shí)現(xiàn)的俭茧。
  3. block內(nèi)部對于成員變量的訪問也是通過block結(jié)構(gòu)體對象的成員self 指針引用來實(shí)現(xiàn)的咆疗。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市母债,隨后出現(xiàn)的幾起案子午磁,更是在濱河造成了極大的恐慌,老刑警劉巖毡们,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迅皇,死亡現(xiàn)場離奇詭異,居然都是意外死亡漏隐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門奴迅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來青责,“玉大人,你說我怎么就攤上這事取具〔绷ィ” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵暇检,是天一觀的道長产阱。 經(jīng)常有香客問我,道長块仆,這世上最難降的妖魔是什么构蹬? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮悔据,結(jié)果婚禮上庄敛,老公的妹妹穿的比我還像新娘。我一直安慰自己科汗,他們只是感情好藻烤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般怖亭。 火紅的嫁衣襯著肌膚如雪涎显。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天兴猩,我揣著相機(jī)與錄音期吓,去河邊找鬼。 笑死峭跳,一個(gè)胖子當(dāng)著我的面吹牛膘婶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛀醉,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼悬襟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拯刁?” 一聲冷哼從身側(cè)響起脊岳,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎垛玻,沒想到半個(gè)月后割捅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帚桩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年亿驾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片账嚎。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡莫瞬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出郭蕉,到底是詐尸還是另有隱情疼邀,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布召锈,位于F島的核電站旁振,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涨岁。R本人自食惡果不足惜拐袜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梢薪。 院中可真熱鬧阻肿,春花似錦、人聲如沸沮尿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赴邻,卻和暖如春印衔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姥敛。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工奸焙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人彤敛。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓与帆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親墨榄。 傳聞我的和親對象是個(gè)殘疾皇子玄糟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容