iOS block 的本质

Posted WeaterMr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS block 的本质相关的知识,希望对你有一定的参考价值。

block 本质

 struct __block_impl {
      void *isa; // isa指针
      int Flags;
      int Reserved;
      void *FuncPtr; // func
    };
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

构造函数__main_block_impl_0的第一个参数为__main_block_func_0方法实现地址,在声明定义block时,将block的任务函数封装到FuncPtr属性中
block 本质是一个oc对象,封装了函数调用。
构造函数类似于oc的init方法,返回结构体对象。

block的定义

block其实相当于调用了一个构造函数,将对应定义的函数地址以及对应结构体的大小当作参数传给构造函数,构造完成后,将对应的对象地址,赋值给block 变量。
在结构体中当,我们可以把结构体强制转化成为第一个成员变量,因为结构体指针的起始地址即为第一个成员的地址,因此可以强制转化。通过内存平移也能取到第一个成员结构体中的数据

block变量捕获机制

block会有一个变量捕获机制。(捕获即新增一个成员,将对应的值复制到内部

  • 1.局部变量 auto (自动变量,离开大括号自动销毁)值传递
  • 2.静态局部变量 static 指针传递 可以捕获到block 内部(这里的区别,主要是对应的变量内存存储性质,auto 出作用域就释放,所以只能值转递。)跨函数访问所以需要捕获。
  • 3.全局变量:不能捕获到block内部,直接访问
  • 4.self也会被捕获到block中,因为我们每个方法的调用其实包含两个隐藏参数selfsel
    从这里可以看出,self也是一个局部变量。对于对象中的属性,成员变量,其实是通过捕获self去访问的。不可能你一个对象有100个属性,我要捕获一百个变量,太臃肿。
    self其实只是告诉我门当前方法是被哪个对象调用。

block类型

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 1. 内部没有调用外部变量的block
        void (^block1)(void) = ^{
            NSLog(@"Hello");
        };
        // 2. 内部调用外部变量的block
        int a = 10;
        void (^block2)(void) = ^{
            NSLog(@"Hello - %d",a);
        };
       // 3. 直接调用的block的class
        NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
            NSLog(@"%d",a);
        } class]);
    }
    return 0;
}
 __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__

//通过weak修饰也在栈区
void(^__weak strongBlock)(void) = ^{
            NSLog(@"---%@", a);
        };

block 继承自NSBlock类型 跟类也是继承自NSObject 所以里面的isa就是继承自NSObgect
block类型:block 在编译时类型相同,但是在运行时类型各不相同,所以,block的类型以运行时为准
三种类型

  • 1.GlobalBlock__(没有访问auto变量,调用copy方法什么也不做。)
  • 2.StackBlock__(MRC访问auto变量,在栈上的block 其实会随着函数被调用,内部的内存会被释放,也即是访问变量对应内存中的值会出现混乱。)
  • 3.MallocBlock__(ARC(自动调用copy)访问auto变量 或 __NSStackBlock__调用了copy ,即调用copy方法。block本质就是对象。而对于堆上的block 调用copy 只是引用计数加一。)
    clane其实是LLVM中的一部分。LLVM在运行时会生成中间文件,这里并不完全是通过clane的到的c++文件。
    疑问?
    堆上的block 调用copy 方法是否仅仅对引用计数的添加?还是新开辟内存。
    引用计数加一
    ARC环境下,编译器会根据情况自动将栈上的block复制到堆上
  • 1.将block赋值给一个强指针时,
  • 2.将block当函数返回值时,
  • 3.在系统中函数中UsingBlock字样也,
  • 4.gcd中的block也是进行copy放到堆区。
  • 5空间将持有对象
  • 6栈上的对象生命周期和堆上的有差异。作用域不同。

block内部引用对象时

        void(^blk)(void);
        Person *person = [[Person alloc] init];
        person.age = 10;
        blk = ^{
             NSLog(@"------block内部%ld",person.age);
        };
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__weak修饰的变量

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__weak weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

当block内部访问了对象类型的auto变量,如果block是栈上,将会对auto变量产生强引用(这里应该会有强引用,因为你不能保证在栈上使用的时候堆上的对象是否被释放)这也就解释当捕获强引用变量时会引用计数加2.
当block被强引用会自动调用copy操作copy到堆上,同时会自动调用
两个函数:

  • 1._Block_object_assign 会根据内部捕获变量强引用修饰和弱引用修饰进行引用,所以根据修饰类型操作引用计数。
  • 2._Block_object_dispose函数是在堆上的block被废弃时调用。

注意:

  • 1.当我们循环嵌套block时,尽管第一层是弱引用,内层是强引用,这时被引用的对像并不会立即释放,会等到内部block使用完毕后释放
  • 2.在block中能改变的变量有 静态变量全局变量__block修饰的临时变量(建议这种方式)。

解决block循环引用方式

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

相互持有,循环引用。

   self.block = ^(ViewController *vc){
       dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2           * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
           NSLog(@"%@",vc.name);
        });
   };
   self.block(self);

这里为什么会有问题 ,因为这个延时操作执行的时候,如果VC被释放,那么就会崩溃。

  • 1.通过 __weak
  • 2.unsafe unreain修饰
  • 3.通过__block ,但是要在block中将对应的使用变量置空,所以要解决需要 必须要调用block。
  • 4.MRC是不支持__weak

__block修饰作用

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

            (a->__forwarding->a) = 10;
}

__block修饰的变量会包装一个对象。当在block中修改外部__block修饰的变量时其实是通过block中的指向该封装对象的结构体拿到该结构中的一个指向自己的一个指针,在通过这个指针拿到age进行赋值
__block修饰其实有一定的性能消耗,只有当对某一个变量进行赋值操作时才需要用__block修饰

NSMutableArray*arr = 【 NSMutableArray new】
blk = ^{
arr=nil(会报错因为赋值操作外部变量要用__block修饰)
[array add object:123](不会因为这里只是使用array这个指针,并没有赋值。)
};

  • 1.可以用于解决block内部无法修改的auto变量的问题
  • 2.不能修饰全局变量,静态变量(static)(原因很简单,这两个变量本来在当前文件任何地方都是可以更改的,不需要__block修饰)
  • 3.编译器会将__block变量包装成一个对象。
  • 4.block在修改NSMutableArray时,需要添加__block吗?
    不需要,这里只是使用,并没有进行初始化。

block的属性修饰词为什么使用copy?

主要目的是将block从栈上copy到堆上,(ARC环境strong修斯也可以做到,因为block有一个特性被强引用指针引用制动调用copy。copy到堆上目的就是自己控制block的生命周期。
在使用__weak typeof (self) weakSelf = self
self.block= {
__stoong typeof(weakSelf) myself = weakSelf;
}
这样做的目的是为了保证当前使用的变量在我block没有执行完不能被释放掉。(因为当前的block不一定属于self)

以上是关于iOS block 的本质的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发系列-Block本质篇

IOS Block的本质

iOS底层探索之Block——Block的本质

iOS底层原理总结 - 探寻block的本质

iOS学习之代码块(Block)

iOS block 的本质