美文网首页
OC学习-block

OC学习-block

作者: 小怪兽大作战 | 来源:发表于2021-04-06 22:55 被阅读0次

block是c语言的扩充功能。用一句话概括block的功能:带有局部变量的匿名函数。

block语法

^ 返回值类型 参数列表 表达式 如:^int (int count) { return count + 1}
返回值类型可以省略。返回值类型省略的时候,如果reture有返回值,就用该返回值类型,如果没有return,返回值类型就是void
^ 参数列表 表达式 如:^ (int count) {return count + 1}
如果不需要番薯,参数列表也可以省略
^ 表达式 如:^ {return 1;}

block类型的变量

将block赋值给block变量。
c语言函数指针 返回值类型 (指针名) (返回值类型) 如:int (funcptr) (int)
block变量类型 返回值类型 (^block变量名) (返回值类型) 如:int (^blockname) (int)

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

int (^blk1) (int) = blk;
int (^blk2) (int);
blk2 = blk1;

使用typedef定义block类型
typedef int(^blk_t) (int);
void func(blk_t blk)
blk_t func(){
}

当然,也可以使用block指针
type int (^blk_t) (int);
blk_t blk = ^int (int count) {return count + 1};
blk_t blkprt = &blk;
(
blkprt)(10);

block保存上下文

block会保存调用时外部变量的值。
比如

int val = 10;
void ^(blk) (void) = ^{printf(val);}
val = 20;
blk();
打印结果 10

在block中改变上下文的值会报错的

int val = 10;
void ^(blk) (void) = ^{val = 20}
val = 20;
编译报错

使用__block说明符修饰变量,可以在block内部改变外部变量的值

__block int val = 10;
void ^(blk) (void) = ^{val = 20}
blk();
print(val)
打印结果 20

自动保存的oc对象类型的变量,使用不会报错,赋值会报错

id array = [NSMutableArray alloc]init];
void (^blk) (void) = ^void (void) {
        id obj = [NSObject new];
        [array addObject:obj]; 
}
不会报错
id array = [NSMutableArray alloc]init];
void (^blk) (void) = ^void (void) {
        array = [NSMutable alloc]init];
}
报错

需要加上__block

__block id array = [NSMutableArray alloc]init];
void (^blk) (void) = ^void (void) {
        array = [NSMutable alloc]init];
}
不报错

block没有实现C语言数组的自动保存

const char text[] = "hello"
void (^blk) (void) = ^ {
        print(text[2]);
}
报错

需要使用指针

const char * text = "hello"
void (^blk) (void) = ^ {
        print(text[2]);
}
不报错

总结:
1.block会自动保存使用的外部局部变量。但是不会保存使用的C语言数组。
2.block不能给使用的外部局部变量赋值。如果要赋值(1.使用全局变量、静态变量、全局静态变量 2.使用__block)

block实现

oc中的block可以类比oc中的类。
声明一个block,就相当于声明一个类。包含了函数指针,指向元block结构体的指针isa。


image.png

block的自动保存变量

block中使用的外部变量被当成一个成员变量,并使用外部变量的值初始化成员变量,添加到生成的block结构体中。只有block中使用的成员变量才会被添加到结构体中。
这样,在block中是不能改变外部变量的。

block中使用全局静态变量、全局变量和局部静态变量,不会在block结构体中创建新变量,直接使用的原始变量。

我认为,block会保存局部变量,是因为block在调用的使用,已经超过了局部变量的声明周期,所以要做一份克隆。

__block修饰的变量,会变成结构体(a),然后block创建的结构体中会引用这个结构体(a)。

总结:block中要改变外部变量
1.使用全局变量、静态变量、全局静态变量
2.使用__block

block存储域

Block也是OC对象。
Block按照存储区域的不同,分为以下类

NSConcreteStackBlock  在栈上分配内存
NSConcreteBlobalBlock  在数据区分配内存
NSConcreteMallocBlock   在堆中分配内存
image.png

大部分情况下,block都在栈上分配内存,对应的类是NSConcreteStackBlock。
当在声明全局变量的地方使用block,或者block中不使用外部的局部变量的时候。block存放在数据区。

Block作为返回值的时候,编译器会自动克隆该block并放置到堆上面。
但是某些情况下,编译器不会自动复制block,需要手动复制。想方法或函数的参数中传递block时,一般需要复制一下。

- (id) getBlockArray
{
        int val = 10;
        return [[NSArray alloc] initWithObjects:
        ^{NSLog(@"block1:%@",val)},
        ^{NSLog(@"block2:%@",val)},nil];
}

在从该方法获取block的时候,就会报错。
需要copy一下再放入
- (id) getBlockArray
{
        int val = 10;
        return [[NSArray alloc] initWithObjects:
        [^{NSLog(@"block1:%@",val)} copy],
        [^{NSLog(@"block2:%@",val)} copy],nil];
}

不同的block调用copy有什么效果呢?
NSConcreteStackBlock 从栈复制到堆中
NSConcreteGlobalBlock 什么都不做
NSConcreteMallocBlock 引用记数增加
对Block调用copy,没有什么问题。多次调用也没什么问题。
总结:往函数中传递block的时候,最好copy一下。

例子



#import "ZiZhongBlockTest.h"

void (^globalblk)(void) = ^{NSLog(@"globalblk");};

@implementation ZiZhongBlockTest

- (void)testBlockWithoutParam:(void (^)(void))block {
    NSLog(@"testBlockWithoutParam:%@",[block class]); //__NSGlobalBlock__
    block();
}

- (void)testGlobalBlock {
    NSLog(@"testGlobalBlock:%@",[globalblk class]); //__NSGlobalBlock__
    globalblk();
}

- (void)testStackBlock:(void (^)(void))block {
    NSLog(@"testStackBlock:%@",[block class]); //__NSStackBlock__
    block();
}

- (id)getStackBlock {
    int val = 10;
    //栈上面分配的block会被销毁,导致错误
    return [[NSArray alloc]initWithObjects:^{NSLog(@"stackBlk1,%d",val);}, ^{NSLog(@"stackBlk1,%d",val);}, nil];
    // 改成copy
}

@end

调用

    ZiZhongBlockTest * blockTest = [ZiZhongBlockTest new];
    NSInteger i = 0;
    [blockTest testBlockWithoutParam:^{
    }];
    [blockTest testGlobalBlock];
    [blockTest testStackBlock:^{
        NSLog(@"%ld",(long)i); // i为局部变量时,block在栈上分配。i为全局变量或静态变量时,在数据区分配。
    }];
    id blkArray = [blockTest getStackBlock];
    typedef void (^myStackblk)(void);
    myStackblk blk = (myStackblk)[blkArray objectAtIndex:0];
    blk(); //报错

总结:
1.定义静态变量的地方创建block,会保存在代码区
2.block不使用外部变量,或使用全局变量、全局静态变量、局部静态变量时,会保存在代码区
3.block使用局部变量和局部静态变量,会保存在栈中
4.往数组、函数中添加block,最好copy一下。

__block变量存储域

block从栈上被复制到堆上时,如果block使用了__block变量,__block变量也会受到影响。
__block变量存储区域 | block从栈上复制到堆上时的影响
栈 | __block变量从栈上复制到对上,并被block持有
堆 | 不变

之前我们知道,使用__block修饰变量之后,会创建一个结构体,然后结构体中__forwarding指针指向了真正的变量。
__block复制到堆上之后,原来栈上__block结构体的__forwarding指针会指向堆上的__block结构体。因此复制之后,栈上的__block变量和堆上的__block变量指向的都是同一个结构体。

block中持有局部变量对象,当block被复制到堆中时,block内部会持有局部变量对象的强引用并增加局部变量对象的引用记数,在堆上的block被废弃时,会自动调用dispose函数废弃局部变量对象。
比如


image.png image.png

总结:

1.block会自动保存使用的外部局部变量。但是不会保存使用的C语言数组。
2.block不能给使用的外部局部变量赋值。如果要赋值(1.使用全局变量、静态变量、全局静态变量 2.使用__block)
3.block也是一种类,有三种类型,分别是NSConcreteStackBlock(栈上分配内存)、NSConcreteBlobalBlock(数据区分配内存)、NSConcreteMallocBlock(堆中分配内存)。
4.block使用外部局部变量,会在栈上分配内存。
5.block不使用外部变量,或者使用静态变量、全局变量、全局静态变量,会在数据区中分配内存
6.创建的静态block,会在数据区分配内存
7.使用__block修饰变量,会创建一个结构体,然后结构体中保存变量的值。通知结构体有一个__forawrd指针,指向结构体真正的地址。调用__block变量a时,其实调用的是a->__forawrd->val
8.不同block使用copy,效果也不一样
NSConcreteStackBlock 从栈复制到堆中
NSConcreteGlobalBlock 什么都不做
NSConcreteMallocBlock 引用记数增加
对Block调用copy,没有什么问题。多次调用也没什么问题。
9.block从栈中复制到堆中时,如果block中使用了__block变量,__block变量也会在堆中复制一份。同时栈中__block变量的__forwarding会指向堆中__block变量。保证栈中操作__block变量和堆中效果一样。
10.栈上block被复制到堆中的时机(1.调用block的copy方法、2.block作为返回值返回时 3.block赋值给strong修饰的id类型的类 3.在方法名中含有usingBlock的cocoa框架或Grand Center Dispatch的API中传递block时)
11.除了上面提到的三种情况,其他情况最好调用block的copy方法。

相关文章

  • OC学习-block

    block是c语言的扩充功能。用一句话概括block的功能:带有局部变量的匿名函数。 block语法 ^ 返回值类...

  • swift 调用 OC中的block

    OC中声明block; OC中实现block swift中实现

  • Swift 之闭包

    闭包 闭包类似于 OC 的 block,但是比 OC 的 block 应用面更广 在 OC 中 block 是匿名...

  • OC-简单粗暴理解Block的本质

    block简单粗暴的理解 OC的block底层就是个OC对象,包含isa指针,封装了函数的调用. OC的block...

  • iOS Block本质笔记

    OC中定义block block访问外部参数 OC转C++分析 block的变量捕获机制 为了保证block能够正...

  • Swift之闭包

    前言 闭包类似于OC的block,但是比OC的block应用面更广 在OC中block是匿名函数 在swift中函...

  • iOS&Swift&OC 闭包和Block的相互转化

    一、Swift的闭包 -> OC的block 二、OC的block -> Swift的闭包

  • iOS原生&JS交互

    OC 获取js中的key OC调用js方法 JS调用OC中不带参数的block JS调用OC中带参数的block ...

  • Block 与 Closure

    Block In OC block 分为以下三种: _NSConcreteStackBlock:栈block,引用...

  • OC学习之block

网友评论

      本文标题:OC学习-block

      本文链接:https://www.haomeiwen.com/subject/tqwekltx.html