iOS 开发 block深入浅出详解

block的定义与使用

/*
*1.最简单的定义方式:
*格式:void (^myBlock)() = ^ { // 代码实现; }
*/
void (^myBlock)() = ^ {
    NSLog(@"hello");
};

// 执行时,把block当成函数
myBlock();

/*
*2.定义带参数的block:
*格式:void (^block名称)(参数列表) = ^ (参数列表) { // 代码实现; }
*/
void (^sumBlock)(int, int) = ^ (int x, int y) {
    NSLog(@"%d", x + y);
};

sumBlock(10, 20);

/*
*3.定义带返回值的block
*格式:返回类型 (^block名称)(参数列表) = ^ 返回类型 (参数列表) { // 代码实现; }
*/
int (^sumBlock2)(int, int) = ^ int (int a, int b) {
    return a + b;
};

NSLog(@"%d", sumBlock2(4, 8));

block 指针

//声明一个名字为square的Block Pointer,其所指向的Block有一个int输入和int输出  
int (^square)(int);  

//block 指针square的内容
square = ^(int a){ return a*a ; };  

//调用方法,感觉是是不是很像function的用法?  
int result = square(5);  
NSLog(@"%d", result); 

用typedef先声明类型,再定义变量进行赋值

typedef int (^MySum)(int,int);
MySum sum = ^(int a,int b)
 { 
      return a + b;
};

block 访问外部变量

  • block内部可以访问外部变量;
  • 默认情况下block内部不能修改外面的局部变量;
  • 给局部变量加上关键字_block,这个局部变量就可以在block内部修改;
int sum = 10;

int (^MyBlock)(int) = ^(int num) 
{ 
     sum++;//编译报错 
     return num * sum; 
};

但是block使用有个特点,Block可以访问局部变量,但是不能修改:

//如果要修改就要加关键字 __block (下面详细说明):
__block int sum =10;

ARC环境:block存储类型和访问修改外部变量

  • 前提是ARC : Block访问外部的变量

    • 1.ARC环境下,单纯的定义一个block存储在全局区 <NSGlobalBlock: 0x1045c60b0>

    • 2.ARC环境下,block访问外部的变量时这个block存储在堆区 <NSMallocBlock: 0x7fa8fd00c1b0>

      2.1 在block访问这个变量之前,变量在栈区 == 0x7fff53c73bfc
      2.2 在block内部访问这个变量时,变量会被block拷贝到堆区 0x7fe851517880

  • 前提是ARC : Block修改外部的变量

    • 1.ARC环境下,当block修改外部变量的时候,会在堆区 <NSMallocBlock: 0x7f82ac8a6130>

    • 2.在block的外面,即使你使用__block修饰了,那么他的地址依然不变,在栈区 0x7fff5e101bf8

    • 3.在block内部修改外部的变量时,使用__block修饰了外部的变量之后,外部的变量会在堆区 0x7f9d10e0bcc8

#pragma mark - ARC - Block修改外部的变量
// 需求 : 研究block和外部变量的内存的变化
- (void)blockDemo2 {
    __block int num = 10;

    // 在block的外面,即使你使用__block修饰了,那么他的地址依然不变,在栈区 0x7fff5e101bf8
    NSLog(@"num01==%p",&num);

    void(^task2)() = ^{

        // 在block内部修改外部的变量时,使用__block修饰了外部的变量之后,外部的变量会在堆区 0x7f9d10e0bcc8
        NSLog(@"task2 %d =%p",num,&num);

        /*
         提示 : 在block内部修改外部变量是不被允许的
         如果非要修改,那么久需要把外部的变量用 __block 来修饰
         */
        num = 20;
    };

    task2();

    // 当block修改外部变量的时候,会在堆区 <__NSMallocBlock__: 0x7f82ac8a6130>
    NSLog(@"%@",task2);

    // 当block内部修改完外部的变量之后,那么这个变量的就会保存到堆区 0x7f9cd1708ca8
    NSLog(@"num02==%p",&num);
}

#pragma mark - ARC - Block访问外部的变量
// 需求 : 研究block和外部变量的内存的变化
- (void)blockDemo1 {
    int num = 10;
    // 在block访问这个变量之前,变量在栈区 == 0x7fff53c73bfc
    NSLog(@"num01==%p",&num);

    void(^task1)() = ^{
        // 在block内部访问这个变量时,变量会被block拷贝到堆区 0x7fe851517880
        NSLog(@"task1 %d %p",num,&num);
    };

    task1();

    // block的本质是指针对象
    // ARC环境下,block访问外部的变量时存储在堆区 <__NSMallocBlock__: 0x7fa8fd00c1b0>
    NSLog(@"task1==%@",task1);

    // 当block在其内部使用完了外部的变量之后,这个变量又会重新回到栈区 0x7fff5a2d8bfc
    NSLog(@"num02==%p",&num);
}

- (void)blockDemo
{
    void(^task1)() = ^{
        NSLog(@"task");
    };

    task1();

    // block的本质是指针对象
    // ARC环境下,单纯的定义一个block存储在全局区 <__NSGlobalBlock__: 0x1045c60b0>
    NSLog(@"task1==%@",task1);
}

MRC环境:block存储类型和访问修改外部变量

  • 前提是MRC :
    1>MRC下,如果是单纯的block,在常量区
    2> MRC下,如果访问或者修改外部变量,这个block在栈区,
    访问外部变量是,外部变量依然在栈区 0x7fff54accb28 (地址变化了,内存空间没变)
    修改外部变量时,外部变量的地址没有发生变化
- (void)blockDemo3 {
    __block int num = 10;

    // 在栈区 0x7fff576d0b38
    NSLog(@"num01==%p",&num);

    void (^task3)() = ^{
        num = 20;

        // 在栈区 0x7fff576d0b38
        NSLog(@"task3 %d %p",num,&num);
    };

    task3();

    // 在栈区  <__NSStackBlock__: 0x7fff576d0ae0>
    NSLog(@"%@",task3);

    // 在栈区 0x7fff576d0b38
    NSLog(@"num02==%p",&num);
}

- (void)blockDemo2 {
    int num = 10;

    // 栈区 0x7fff54accb3c
    NSLog(@"num01==%p",&num);

    void(^task2)() = ^{

        // 依然在栈区 0x7fff54accb28 (地址变化了,内存空间没变)
        NSLog(@"task2 %d %p",num,&num);
    };
    task2();

    // 存储在栈区 <__NSStackBlock__: 0x7fff5670eb08>
    NSLog(@"%@",task2);

    // 栈区 0x7fff54accb3c
    NSLog(@"num02==%p",&num);
}

- (void)blockDemo1
{
    void(^task1)() = ^{
    };

    task1();

    // 全局/常量区 <__NSGlobalBlock__: 0x10c11a070>
    NSLog(@"%@",task1);
}

block为什么要用copy修饰

// 定义一个块代码的属性,block属性需要用 copy
@property (nonatomic, copy) void (^completion)(NSString *text);
  • 虽然在ARC时代已经不需要再显式声明了,使用strong是没有问题的,但是仍然建 议我们使⽤copy以显示相关拷贝⾏为

  • 默认情况下,block是存档在栈中,可能被随时回收,通过copy操作可以使其在堆中保留一份, 相当于一直强引用着, 因此如果block中用到self时, 需要将其弱化, 通过__weak或者__unsafe_unretained. 以下是示例代码及其说明, 读者可以试着打印出不同情况下block的内存情况

  • 堆区中的block,也就是copy修饰的block,生命周期随着对象的销毁而结束(引用计数变为0),只要引用block的对象不销毁,就可以调用存放在堆区的block,这就是copy修饰block的原因

  • NSConcreteStackBlock(栈区block),需要涉及到外界变量的block在创建的时候是在stack上⾯分配空间的,也就是⼀旦所在函数返回,执行弹栈,则会被摧毁。这就导致内存管理的问题,如果我们希望保存这个block或者是返回它,如果没有做进⼀步的copy处理,则必然会出现问题

  • 在Objective-C语⾔言中,⼀一共有3种类型的block:
    _NSConcreteGlobalBlock 全局的静态block,不会访问任何外部变量。
    _NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。
    _NSConcreteMallocBlock 保存在堆中的block,当引⽤用计数为0时会被销毁。

block引用外部变量时为什么要用__block修饰

  • 堆区的block有一下现象:block内部如果访问外部声明的对象,默认block内部会自动产生一个强指针强引用所指向的这个对象;
  • 如果被访问的外部对象被弱指针修饰,那么block在内部引用的时候,就会自动产生一个弱指针指向所应用的对象

block解除循环引用

OC对象,不同于基本类型,Block会引起对象的引用计数变化。若我们在block中引用到oc的对象,则对象的引用计数器会加1, 不过在对象前 加__block修饰,则参考计数不变。

__weak typeof(self) weakSelf = self;

block传值与回调

参考:block 使用详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值