【iOS】blocks

block

什么是block?

blocks是C语言的扩充功能。用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。

但是在C语言的标准中不允许存在匿名函数。通过Blocks,源代码中就可以使用匿名函数。

block的本质

blocks是C语言的扩充功能。用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。
但是在C语言的标准中不允许存在匿名函数。通过Blocks,源代码中就可以使用匿名函数。

  • block本质上也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象
  • block是封装函数及其上下文的OC对象

block的变量捕获

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

在这里插入图片描述

block语法

标准格式
在这里插入图片描述
例如:

^int (int count){return count + 1;}

返回值类型默认void
如果是void 我们也可以默认省略(void)
在这里插入图片描述

Block变量

  • Block变量类似于函数指针
  • 声明Block类型变量仅仅是将声明函数指针类型变量的"*“变为”^"
int (^blk)(int)

block变量与c语言变量完全相同,可以作为以下用途:

  • 自动变量
  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量

block的三种类型

block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

  • __NSGlobalBlock __ ( _NSConcreteGlobalBlock ) 对象存储在数据区
  • __NSStackBlock __ ( _NSConcreteStackBlock ) 对象存储在栈区
  • __NSMallocBlock __ ( _NSConcreteMallocBlock )对象存储在堆区

简单来说:

  • 捕获了自动变量 就是Block就是栈类型
  • 没有捕获就是数据区
  • 不会有一创建就在堆区的,堆区的意义可以理解为和autorelease一样:延长作用域 Stack类型的Block进行了copy操作之后变成了堆区
  • 堆:动态分配内存,需要程序员自己申请,程序员自己管理
  • 栈:自动分配内存,自动销毁,先入后出,栈上的内容存在自动销毁的情况

NSGlobalBlock

如果一个 block 没有访问外部局部变量,或者访问的是全局变量,或者静态局部变量,此时的 block 就是一个全局 block ,并且数据存储在全局区。

	//block1没有引用到局部变量
    int a = 10;
    void (^block)(void) = ^{
         NSLog(@"hello world");
    };
    NSLog(@"block:%@", block);

	//	block2中引入的是静态变量
    static int a1 = 20;
    void (^block)(void) = ^{
        NSLog(@"hello - %d",a1);
    };

    NSLog(@"block:%@", block);

请添加图片描述

NSStackBlock

Block名前面加个__weak就是栈区block

    __block int a = 10;
    static int a1 = 20;
    void (^__weak block)(void) = ^{
        NSLog(@"hello - %d",a);
        NSLog(@"hello - %d",a1);
    };
    NSLog(@"block:%@", block);

运行结果:
请添加图片描述

NSMallocBlock

Block里有引用到局部变量时为堆区block

    	int a = 10;
        void (^block1)(void) = ^{
            NSLog(@"%d",a);
        };

        NSLog(@"block1:%@", block1);
    
        __block int b = 10;
        void (^block2)(void) = ^{
            NSLog(@"%d",b);
        };

        NSLog(@"block2:%@", block2);

运行结果

请添加图片描述

block继承于NSObject

代码演示示例:

void (^block1)(void) = ^{
    NSLog(@"block1");
};
NSLog(@"%@",[block1 class]);
NSLog(@"%@",[[block1 class] superclass]);
NSLog(@"%@",[[[block1 class] superclass] superclass]);
NSLog(@"%@",[[[[block1 class] superclass] superclass] superclass]);
NSLog(@"%@",[[[[[block1 class] superclass] superclass] superclass] superclass]);

运行结果:
请添加图片描述

  • 上述代码输出了block1的类型,也证实了block是对象,最终继承NSObject

代码展示block的三种类型:

int age = 1;
void (^block1)(void) = ^{
    NSLog(@"block1");
};

void (^block2)(void) = ^{
    NSLog(@"block2:%d",age);
};

NSLog(@"%@/%@/%@",[block1 class],[block2 class],[^{
    NSLog(@"block3:%d",age);
} class]);

输出结果:
请添加图片描述

堆Block和栈Block的区别

示例一:

    __block int a = 10;
    __block int b = 20;
    NSLog(@"a:%p---b:%p", &a, &b);
    void (^__weak block)(void) = ^{
        NSLog(@"hello - %d---%p",a, &a);
        a++;
    };
    void (^block1)(void) = ^{
        NSLog(@"hello - %d---%p",b, &b);
        b++;
    };
    block();
    block1();
    NSLog(@"block:%@---block1:%@", block, block1);
    NSLog(@"a:%d---b:%d", a, b);
    NSLog(@"a:%p---b:%p", &a, &b);

输出结果:

请添加图片描述

  • 通过结果我们看到,首先block的地址是在栈区,而block1的地址是在堆区,而栈block引用的变量a的地址并没有变化,而堆block1引用的变量b的地址也相应变成了堆区0x283e0b1b8,并且后面使用的b的地址都是堆区上的。

总结:栈block存放在栈区,对局部变量引用只拷贝局部变量的地址,而堆block存放在堆区,并且直接将局部变量拷贝了一份到堆空间。

示例二:

	NSObject *objc = [NSObject new];
    NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));// 1
    // block 底层源码
    // 捕获 + 1
    // 堆区block
    // 栈 - 内存 -> 堆  + 1
    void(^strongBlock)(void) = ^{ // 1 - block -> objc 捕获 + 1 = 2
        NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    strongBlock();

    void(^__weak weakBlock)(void) = ^{ // + 1
        NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    weakBlock();
    
    void(^mallocBlock)(void) = [weakBlock copy];
    mallocBlock();

输出结果:
请添加图片描述

奇怪为什么堆区block里面的对象引用计数加2呢?而后面的mallocBlock只加1呢?

  • 首先objcstrongBlock里面必然会拷贝一份到堆区,所以会加1,但是他是从当前函数的栈区拷贝吗?并不是,对于堆区Block一开始编译时是栈block这时候objc对象地址拷贝了一份引用计数加1,后面从栈block变成堆block,又拷贝了一份引用计数又加1,所以这时候是3,weakBlock是栈block仅拷贝了一份,所以引用计数加1,这时候是4,mallocBlockweakblock拷贝了一份,所以引用计数再加1,这时候是5,相当于strongBlock = weakblock + void(^mallocBlock)(void) = [weakBlock copy];

示例三:

    NSObject *a = [NSObject alloc];
    NSLog(@"1---%@--%p", a, &a);
    void(^__weak weakBlock)(void) = nil;
    {
        // 栈区
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"2---%@--%p", a, &a);
        };
        weakBlock = strongBlock;
        strongBlock();
        NSLog(@"3 - %@ - %@",weakBlock,strongBlock);
    }
    weakBlock();
    NSLog(@"4---%@--%p", a, &a);

请添加图片描述

  • 当前是栈区strongBlock的赋值给外面的栈区weakBlock,因为都是存放在栈空间的,只有当前函数结束才会被销毁,随意这边weakBlock调用并不会有什么问题。如果换成堆区block就不一样了。

注意:这边的a对象在weakBlock()调用时是nil,通过上面打印可以看出a对象在进入到strongblock里,&a拷贝了一份,拷贝的这一份地址指向的跟外面一样,但是当strongblock出了{},尽管strongblock对象不再了,但是其指向的内存空间还在,销毁之前给了外面的weakBlock,同理a也一样,对象(此时a指向的内容)不在了,但是内存空间却还在。

示例四:

NSObject *a = [NSObject alloc];
//    NSLog(@"1---%@--%p", a, &a);
    void(^__weak weakBlock)(void) = nil;
    {
        // 堆区
        void(^strongBlock)(void) = ^{
            NSLog(@"2---%@--%p", a, &a);
        };
        weakBlock = strongBlock;
        strongBlock();
        NSLog(@"3 - %@ - %@",weakBlock,strongBlock);
    }
    weakBlock();
//    NSLog(@"4---%@--%p", a, &a);

请添加图片描述

  • 为什么呢?因为在{}里面的堆区strongBlock出了大括号就会被销毁,此时你去调用这个block就会崩溃

注意:这边weakBlock为什么也是__NSMallocBlock__,其实weakBlock相当于是指针,此时指向的是一个堆上的内存所以是__NSMallocBlock__

block的循环引用

内存泄漏一个主要原因就是block的循环引用。那么如何解决循环引用呢?

- (void)viewDidLoad {
    [super viewDidLoad];
    // 循环引用
    self.name = @"hongfa";
    self.block = ^{
        NSLog(@"%@", self.name);
    }
    self.block();
}

这边vc 引用了block ,block引用了vc,最后面造成了循环引用,那么如何解决呢?
方案一:通过weak来解决

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值