iOS 进阶

OC中的Block

2021-02-25  本文已影响0人  苍眸之宝宝

1.Block的本质

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __showBlockBase_block_impl_0 {
  struct __block_impl impl;
  struct __showBlockBase_block_desc_0* Desc;
  __showBlockBase_block_impl_0(void *fp, struct __showBlockBase_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __showBlockBase_block_func_0(struct __showBlockBase_block_impl_0 *__cself) {

        printf("hello word!\n");
    }

static struct __showBlockBase_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __showBlockBase_block_desc_0_DATA = { 0, sizeof(struct __showBlockBase_block_impl_0)};

// Block的定义和调用
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    // 定义Block
    void (*blockOne)(void) = ((void (*)())&__showBlockBase_block_impl_0((void *)__showBlockBase_block_func_0, &__showBlockBase_block_desc_0_DATA));
    // 调用Block
    ((void (*)(__block_impl *))((__block_impl *)blockOne)->FuncPtr)((__block_impl *)blockOne);
  }
}

  由于现在的环境基本上都是ARC环境,所以这篇文章默认是在ARC环境下的情况。

2.Block捕获变量

  OC中变量有5种:自动变量(局部变量)、函数的参数(函数形参)、静态变量(静态局部变量)、静态全局变量、全局变量;对于Block捕获自动变量时,还有可能被__block修饰的自动变量。
  当Block捕获静态全局变量、全局变量和未捕获外部的情况相同,底层代码如下;

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __showBlockBase_block_impl_0 {
  struct __block_impl impl;
  struct __showBlockBase_block_desc_0* Desc;
  __showBlockBase_block_impl_0(void *fp, struct __showBlockBase_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

  当Block捕获静态变量时,Block在底层解释为相关类时会增加一个指针类型的属性变量,该指针类型的值类型与该静态变量的值类型相对应;

    // 局部静态变量
    static int staticValue = 0;

struct __showBlockBase_block_impl_3 {
  struct __block_impl impl;
  struct __showBlockBase_block_desc_3* Desc;
  int *staticValue;  // 捕获的外界局部静态变量
  __showBlockBase_block_impl_3(void *fp, struct __showBlockBase_block_desc_3 *desc, int *_staticValue, int flags=0) : staticValue(_staticValue) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

  当Block捕获自动变量时,Block在底层解释为相关类时会增加一个的属性变量与该自动变量相对应;

    // 自动变量
    int value = 0;

struct __showBlockBase_block_impl_4 {
  struct __block_impl impl;
  struct __showBlockBase_block_desc_4* Desc;
  int value;  // 捕获的外界自动变量
  __showBlockBase_block_impl_4(void *fp, struct __showBlockBase_block_desc_4 *desc, int _value, int flags=0) : value(_value) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

  当Block捕获__block修饰的自动变量时,Block在底层解释为相关类时会增加一个指针类型的属性变量,且该属性变量的类型是该类型对应的一个类,类中的一个属性变量的值与该自动变量相对应。

    // block变量
    __block int i = 0;

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __showBlockBase_block_impl_5 {
  struct __block_impl impl;
  struct __showBlockBase_block_desc_5* Desc;
  __Block_byref_i_0 *i;  // 捕获的外界__block修饰的自动变量
  __showBlockBase_block_impl_5(void *fp, struct __showBlockBase_block_desc_5 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

3.Block的类型

  Block根据在内存分区的位置,可以分为三类:全局Block(NSGlobalBlock)即在静态全局区的Block、栈Block(NSStackBlock)即在栈上的Block、堆Block(NSMallocBlock)即在堆上的Block。
  当Block在捕获静态变量、静态全局变量、全局变量、未捕获外部变量时,为全局Block。
  当Block定义后被赋值给了某个变量或者某个类的属性时,在这个过程中会执行 Block_copy将原有的NSStakeBlock变成NSMallocBlock;
  当Block定义后没有赋值给某个变量,那它的类型就是NSStakeBlock。

4.Block的内存管理

  Block首次定义是在栈上,将声明的Block赋值给变量或者某个类的属性时,会调用objc_retainBlock方法,在该方法中会调用_Block_copy将栈上的Block复制到堆上;因此声明一个Block的属性一般使用copy关键字。
  栈上的Block在执行完毕后会立即释放,而堆上的Block通过ARC机制管理自己的内存。

5.Block的循环引用

  栈上的Block在执行完毕后就会释放,一般不会导致循环引用;我们所说的循环引用是堆上的Block形成。
  Block循环引用的原因:对象间接或者直接持有Block,Block中间接或者直接持有该对象,就会造成循环引用,导致对象和Block内存无法释放。
  避免Block循环引用有三种方法:
1.创建一个__weak修饰的指针变量,将造成循环引用的对象的赋值该指针变量,在Block中使用该指针变量,打破循环引用。代码如下:

    __weak typeof(self) weakSelf = self;
    self.myBlock3 = ^ {
        dispatch_after(2, dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakSelf);
        });
    };
    // 调用
    self.myBlock3();

2.在Block可执行完毕后将所持有对象设置为空,但是如果该Block没有执行,依旧不能打破循环引用。代码如下:

    __block UIViewController * vc = self;
    self.myBlock = ^(int sendValue) {
        dispatch_after(2, dispatch_get_main_queue(), ^{
            NSLog(@"%@", vc);
            vc = nil;
        });
    };
    // 调用
    self.myBlock(2);

3.将持有对象作为Block的参数传入。代码如下:

    self.myBlock2 = ^(UIViewController *vc) {
        dispatch_after(2, dispatch_get_main_queue(), ^{
            NSLog(@"%@", vc);
        });
    };
    // 调用
    self.myBlock2(self);

6.Block的弱引用和多线程的问题

  在Block内部使用外部用__weak 修饰的弱引用。我们知道弱引用指向的对象在没有强引用引用的时候就会自动销毁,同时弱引用的指针赋值为 nil。那么如果在Block里面使用了多线程来访问弱引用,因多线程执行的时间顺序不定,会造成在多线程访问对象的时候这个弱引用的对象已经销毁了。

    Person * person = [[Person alloc] init];
    // 弱引用对象
    __weak typeof(Person) *weakPerson = person;
    person.name = @"zhou";

    person.block = ^int(int a, int b) {
        // 开辟一条线程
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            // 访问弱引用的对象
            NSLog(@"name = %@", weakPerson.name);
        });
        return 1;
    };
    person.block(1, 2);

  在Block中创建一个__strong修饰的指针变量,将外部的__weak的指针变量复制给该变量。

    Person * person = [[Person alloc] init];
    // 弱引用对象
    __weak typeof(Person) *weakPerson = person;
    person.name = @"zhou";
    person.block = ^int(int a, int b) {
        __strong typeof(Person) * strongPerson = weakPerson;
        // 开辟一条线程
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            // 访问弱引用的对象
            NSLog(@"name = %@", strongPerson.name);
        });
        return 1;
    };
    person.block(1, 2);
上一篇 下一篇

猜你喜欢

热点阅读