Block 之 __block的使用

前言:如何在block內(nèi)修改外部變量的值

在前面,我們有學(xué)習(xí)到過block捕獲局部變量,不捕獲全局變量。
那下面我們來思考一個(gè)問題:我們可以在block內(nèi)部修改外部變量的值嗎?

@implementation ViewController

//全局變量name
NSString *name =@"zhangsan";

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //情況1:age是auto類型的
    //這種情況下,不可以修改age的值,因?yàn)閎lock捕獲變量age進(jìn)去僅僅是age的值,跟外面的age變量沒有關(guān)聯(lián)
    int age = 10;
    void(^myBlock1)(void) = ^{
        //如果在這個(gè)地方修改會(huì)報(bào)錯(cuò)
        //age =20;
        NSLog(@"age is %d",age);
    };
    myBlock1();
    
    
    //情況2:height是static類型的
    //這種情況下,可以修改height的值,因?yàn)閎lock捕獲變量height進(jìn)去的是height的內(nèi)存地址,跟外面的height是一塊內(nèi)存地址,block內(nèi)部修改了,外面的height的值會(huì)跟著變化
    static int height = 160;
    void(^myBlock2)(void) = ^{
        height = 170;
        NSLog(@"height is %d",height);
    };
    myBlock2();
    
    
    //情況3:name是全局的
    //同情況2的道理一樣,當(dāng)然可以修改
    void(^myBlock3)(void) = ^{
        name = @"lisi";
        NSLog(@"name is %@",name);
    };
    myBlock3();
}
@end

以上情況2:變成static變量 和情況3:變成全局變量都可以保存block中能夠修改變量的值,但是這樣變量會(huì)長(zhǎng)久存儲(chǔ)在數(shù)據(jù)區(qū),那有沒有不使變量長(zhǎng)久存在且能夠修改變量的方法呢?使用__block修飾變量可以做到。

1、__block修飾符
#import "ViewController.h"

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block int age = 10;
    void(^myBlock)(void) = ^{
        age =20;
        NSLog(@"age is %d",age);
    };
    myBlock();
}

@end
============================================
打印結(jié)果:age is 20

__block可以用于解決block內(nèi)部無法修改auto變量值的問題、
__block修飾auto變量不會(huì)使變量變成全局變量、
__block不能修飾全局變量、靜態(tài)變量(static)。

2、__block修飾符的本質(zhì)

使用clang命令查看,仔細(xì)看源碼中的注釋喲
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m

struct __Block_byref_age_0 {
  void *__isa; //一般有isa,我們可以將其當(dāng)作一個(gè)OC對(duì)象
__Block_byref_age_0 *__forwarding;//這個(gè)__forwarding其實(shí)指向的是它自己
 int __flags;
 int __size;
 int age;//這個(gè)age才是存貯的10的變量
};


//這個(gè)是block的結(jié)構(gòu)體
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  //之前了解過,如果捕獲的是基本數(shù)據(jù)類型的auto變量時(shí),這里應(yīng)該是int age;
  //但現(xiàn)在可以看出,這個(gè)是捕獲了一個(gè)__Block_byref_age_0結(jié)構(gòu)體,他的結(jié)構(gòu)看上面
  __Block_byref_age_0 *age; // by ref
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
        //這里對(duì)應(yīng)的是block內(nèi),給age賦值的代碼:age =20;
        //可以看出,不是直接給age賦值,而是先取到block結(jié)構(gòu)體內(nèi)部__Block_byref_age_0類型的age,
        //然后通過這個(gè)__forwarding找到對(duì)應(yīng)的內(nèi)存地址,然后再找到__Block_byref_age_0結(jié)構(gòu)體內(nèi)部的int age,給他賦值
        (age->__forwarding->age) =20;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_cp_f1q8npcs2b91f_9szlk2t6f00000gn_T_ViewController_2cbb82_mi_0,(age->__forwarding->age));
    }
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
  void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
     //這個(gè)對(duì)應(yīng)函數(shù)內(nèi)定義age,并用__block修飾age的代碼:__block int age = 10;
     //可以看出,不是單純的給age賦值為10,而是給__Block_byref_age_0這個(gè)結(jié)構(gòu)體的每一項(xiàng)附上對(duì)應(yīng)的值
     //    struct __Block_byref_age_0 {
     //        void *__isa;   -----> 賦值(void*)0
     //        __Block_byref_age_0 *__forwarding;   -----> 賦值&age,也就是自己的內(nèi)存地址
     //        int __flags;   -----> 賦值 0
     //        int __size;   -----> 賦值sizeof(__Block_byref_age_0),也就是自己的size大小
     //        int age;   -----> 賦值10,也就是外部給age的值
     //    };
    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
    void(*myBlock)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}

圖示:
屏幕快照 2018-10-31 上午11.06.11.png

從上面我們可以了解到,用__block修飾變量,可以把我們的變量包裝成一個(gè)對(duì)象,類似于__Block_byref_age_0這樣的一個(gè)結(jié)構(gòu)體,訪問、賦值都是通過age->__forwarding->age這樣的流程來完成的。

3、__block的內(nèi)存管理

下面我們就結(jié)合__block修飾基本數(shù)據(jù)類型、對(duì)象類型auto變量這兩種情況來總結(jié)一下__block的內(nèi)存管理。
基本數(shù)據(jù)類型的auto變量

__block int age = 10;
    void(^myBlock1)(void) = ^{
        age =20;
        NSLog(@"age is %d",age);
    };
==================================================
myBlock1的結(jié)構(gòu)體對(duì)應(yīng):
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
==================================================
其中,__Block_byref_age_0結(jié)構(gòu)體:
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
  • 當(dāng)block在棧上時(shí),并不會(huì)對(duì)__block變量產(chǎn)生強(qiáng)引用
  • 當(dāng)block被copy到堆時(shí)
    <1>會(huì)調(diào)用block內(nèi)部的copy函數(shù)
    <2>copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
    <3>_Block_object_assign函數(shù)會(huì)對(duì)__block變量形成強(qiáng)引用(retain)
  • 當(dāng)block從堆中移除時(shí)
    <1>會(huì)調(diào)用block內(nèi)部的dispose函數(shù)
    <2>dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)
    <3>_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的__block變量(release)

對(duì)象類型的auto變量

__block MJPerson *person = [[MJPerson alloc] init];
    void(^myBlock2)(void) = ^{
        person = nil;
        NSLog(@"person ---> %@", person);
    };
==================================================
myBlock2的結(jié)構(gòu)體對(duì)應(yīng):
struct __ViewController__viewDidLoad_block_impl_1 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_1* Desc;
  __Block_byref_person_1 *person; // by ref
  __ViewController__viewDidLoad_block_impl_1(void *fp, struct __ViewController__viewDidLoad_block_desc_1 *desc, __Block_byref_person_1 *_person, int flags=0) : person(_person->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
==================================================
其中,__Block_byref_person_1結(jié)構(gòu)體:
struct __Block_byref_person_1 {
  void *__isa;
__Block_byref_person_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 MJPerson *person;
};

與__Block修飾基本數(shù)據(jù)類型auto變量不同的點(diǎn),就是這里_Block_object_assign會(huì)根據(jù)對(duì)象類型前面的修飾符是__weak還是__strorng(默認(rèn)且隱藏)來對(duì)對(duì)象類型變量進(jìn)行相應(yīng)的弱引用或是強(qiáng)引用操做。

  • 當(dāng)__block變量在棧上時(shí),不會(huì)對(duì)指向的對(duì)象產(chǎn)生強(qiáng)引用
  • 當(dāng)__block變量被copy到堆時(shí)
    <1>會(huì)調(diào)用__block變量?jī)?nèi)部的copy函數(shù)
    <2>copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
    <3>_Block_object_assign函數(shù)會(huì)根據(jù)所指向?qū)ο蟮男揎椃╛_strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用(注意:這里僅限于ARC時(shí)會(huì)retain,MRC時(shí)不會(huì)retain
  • 如果__block變量從堆上移除
    <1>會(huì)調(diào)用__block變量?jī)?nèi)部的dispose函數(shù)
    <2>dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)
    <3>_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放指向的對(duì)象(release)
4、拓展

問:下面的兩種情況,auto變量需要用__block修飾嗎?

    //情況一:
    NSMutableArray *arr = [NSMutableArray array];
    void(^myBlock1)(void) = ^{
        [arr addObject:@"一"];
        NSLog(@"arr is %@",arr);
    };
    myBlock1();
    
    //情況二:
    MJPerson *person = [[MJPerson alloc] init];
    person.age = 10;
    void(^myBlock2)(void) = ^{
        person.age = 20;
        NSLog(@"person's age is %d",person.age);
    };
    myBlock2();

答:不用,因?yàn)檫@種情況并不是直接修改arr、person這個(gè)變量里面存儲(chǔ)的值,而是把a(bǔ)rr和person拿來用。

5、總結(jié)
  • <1>__block的作用是什么?有什么使用注意點(diǎn)?
    使用__block修飾auto變量,會(huì)把a(bǔ)uto變量包裝成一個(gè)對(duì)象,解決block中無法修改外部auto變量的問題。
    注意點(diǎn):會(huì)產(chǎn)生對(duì)象的內(nèi)存管理,變得相對(duì)復(fù)雜些。
    而且MRC下__block不會(huì)對(duì)OC對(duì)象產(chǎn)生強(qiáng)引用的。

  • <2>block在修改NSMuatbleArray的時(shí)候,需不需要添加__block?
    不需要,如拓展中所說的。

  • <3>__block修飾變量、block捕獲外部對(duì)象類型變量,這兩種情況都是底層調(diào)用copy函數(shù),再調(diào)用_Block_object_assign來對(duì)對(duì)象進(jìn)行強(qiáng)引用或者弱引用,那么兩者之間有什么區(qū)別嗎?
    有區(qū)別的,調(diào)用_Block_object_assign函數(shù)和_Block_object_dispose函數(shù)時(shí),傳入的參數(shù)是不一樣的,蘋果里面應(yīng)該會(huì)根據(jù)傳入type的不同做相應(yīng)的操作。
    (1)、__block變量(假設(shè)變量名叫做a)

copy時(shí):
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); 

dispose時(shí):
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

(2)、對(duì)象類型的auto變量(假設(shè)變量名叫做p)

copy時(shí):
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

dispose時(shí):
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容