美文网首页iOS开发 - 底层原理总结面试集结
iOS中的Block(持续更新,精益求精)

iOS中的Block(持续更新,精益求精)

作者: 非洲小白猿 | 来源:发表于2019-07-01 11:40 被阅读7次

block,我们把它叫做代码块, ^加上{}就组成了一个block, {}后面加上()就可以执行{}里面的代码

^{
   NSLog(@"这就是block");
}()

如果想在用到的时候再调用, 就要用个变量把它存起来

//定义一个block,并用变量存起来
void (^block)() = ^ {
   NSLog(@"这就是block");
}
//调用block
block();

block本质上是一个OC对象, 是封装了函数调用以及函数调用环境的OC对象, 它就像我们知道的其他OC对象一样, 它的内部也有isa指针

block的底层数据结构
block捕获变量(block访问block外面的变量的情况)

下面这段代码执行完毕后, 会输出多少呢?

int a = 100;
void (^block)() = ^ {
   NSLog(@"a is %d",a);
}
a = 200;
block();

上面这段代码, 打印后输出100, 为什么会输出100, 而不是200呢? 这就是我们今天要讨论的block捕获变量.

我们定义的block, 可以在其他要用的地方进行调用,可是局部变量a出了它的作用域, 就被销毁了, 于是在我们调用block的时候, 它要访问的局部变量a可能就不存在了, 也就没法使用这个局部变量a了. 所以block为了能够在使用局部变量a的时候确保能够用, 它把局部变量a在它那里存一份.

下面我们再来看另一段代码, 打印后输出的值是多少呢?

static int b = 100;
void (^block)()  = ^ {
   NSLog(@"b = %d",b);
}
b = 200;
block();

上面这段代码, 打印后输出的值是200. 那么问题来了, 为什么会出现这种差异呢?

因为使用static修饰的变量b, 会一直存在内存中, block可以随时访问static修饰的变量b. 变量b也会存放到block里面去, 不过变量b跟变量a不一样, 变量b是把指针传进了block, 而变量a是把它的值传进了block. 这样无论外面的变量b是否发生改变, block都能使用真正的变量b.

我们再来看这段代码, 打印后输出的值是多少呢?

int a = 100;
static int b = 100;

int main() {
   @autoreleasepool {
      void (^block)(void) = ^{
         NSLog(@"a = %d, b = %d",a,b);
      }
      a = 200;
      b = 200;
      block();
   }
   return 0;
}

上面这段代码输出的200, 200.为什么会这样呢?

因为这两个都是全局变量, 它会一直存在程序中, 不会像局部变量一样出了作用域就销毁, block在任何地方访问它们, 都是可以的. 并且block不会捕获全局变量, 因为block可以随时访问它们, 没必要对它们进行捕获, 而且将它们捕获到block里面, 还要花费资源把它们存起来, 这对程序来说简直是一种浪费.

总结上面的示例代码, 我们发现, 只要是局部变量, block都会捕获它. 我们没有主动使用任何修饰符修饰的变量(系统默认使用auto帮我们修饰了,也叫自动变量), block会对它进行值捕获, static修饰的静态变量, block会对它进行指针捕获. 而全局变量, block不会对它进行捕获.我们把以上总结做成下面这个表格


block捕获机制.png

有了上面的总结, 我们再来看一段代码, 调用下面的test, 会对self进行捕获?

#import "Test.h"

@implementtation Test

- (void)test {
   void (^block)(void) = ^{
      NSLog(@"test:%p",self);
   }
   block();
}

block会对这个self进行捕获, 在我们的iOS中, 所有方法的调用, 底层都是给调用者发送消息, 所有的方法默认都会带两个参数, 方法调用者slef, 和方法名. 所以slef它是一个参数,参数都是局部变量, 所以slef它是一个局部变量. 我们上面已经总结出, 只要是局部变量, block就会捕获, 所以block会对这个self进行捕获.

block的类型

block作为OC对象, 也就可以像OC对象一样调用class方法.

void (^block)(void) = ^{
   NSLog(@"Hello Block!");
};
NSLog(@"%@", [block class]);
NSLog(@"%@",[ [block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
2019-07-01 18:14:03.880499+0800 blockDemo[50007:626703] __NSGlobalBlock__
2019-07-01 18:14:03.880732+0800 blockDemo[50007:626703] __NSGlobalBlock
2019-07-01 18:14:03.880758+0800 blockDemo[50007:626703] NSBlock
2019-07-01 18:14:03.880772+0800 blockDemo[50007:626703] NSObject
Program ended with exit code: 0

我们可以看到, 分别打印了3种不同的block, 和它们的之间的继承关系, 以及block最终的父类NSObject, 它的isa指针也跟其他OC对象一样是从NSObject来的 这也充分说明了block它是OC对象.

3种block在内存中的位置.png

未完待续...

相关文章

网友评论

    本文标题:iOS中的Block(持续更新,精益求精)

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