iOS之Block分类和使用注意事项

Block的分类

Block有三种类型:

  • 全局Block:__NSGlobalBlock,
  • 堆区Block:__NSMallocBlock,
  • 栈区Block:__NSStackBlock

block的三种类型.并都继承于NSBlock
我们在讲block的三种类型之前,先了解一下程序的内存分配情况,因为不同类型的block分配的内存也不同.。

在iOS中内存主要分为五大区域:栈区、堆区、静态区、常量区、代码段

不同block类型的内存分配

 

一: __NSGlobalBlock

结论: 没有访问 auto变量 的block 就是 __NSGlobalBlock

-(void)gloabelBlock{
    static int age = 10;
           void(^block)(void) = ^{
               NSLog(@"Hello, World! %d",age);
           };
           NSLog(@"%@",[block class]);
}

2022-06-03 11:58:58.803486+0800 DemoTest2022[47079:2167980] __NSGlobalBlock__

二: __NSStackBlock

结论:访问了auto变量 的block 就是 __NSStackBlock

-(void)stackBlock{
    int age = 10;
    void(^block)(void) = ^{
        NSLog(@"Hello, World! %d",age);
    };
    NSLog(@"%@",[block class]);
       
}

2022-06-03 12:04:10.401128+0800 DemoTest2022[47193:2173770] __NSMallocBlock__

怎么打印的是NSMallocBlock,刚才不是说访问了auto变量就是__NSStackBlock吗?
因为这里我们使用的是ARC,在ARC环境下,Xcode编译器再某些情况会默认帮我们做调用copy 变成堆block ,我们在Build Settings中把ARC设置成MRC,再来打印一下:

2022-06-03 12:13:28.851386+0800 DemoTest2022[47687:2185431] __NSGlobalBlock__

我们思考一下,__NSStackBlock在访问外部变量时,会有什么问题?

会出现野指针crash 所以在ARC坏境Xcode帮我们处理成了堆block(NSMallocBlock)防止出现释放了还去访问导致野指针crash

三: __NSMallocBlock

结论: 当一个__NSStackBlock调用了copy操作,返回的就是一个__NSMallocBlock// ARC下不用copy会自动copy到堆区

-(void)stackBlock{
    int age = 10;
    void(^block)(void) = [^{
        NSLog(@"Hello, World! %d",age);
    } copy];
    NSLog(@"%@",[block class]);
       
}

2022-06-03 12:29:42.356398+0800 DemoTest2022[48117:2204224] __NSMallocBlock__

四:Block注意事项

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

#import "BlockViewController.h"
typedef void (^Block)(void);
@interface BlockViewController ()
@property(nonatomic,copy) Block block;
@end

@implementation BlockViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //循环引用
    self.name = @"block";
    self.block = ^{
        NSLog(@"%@",self.name);
    };
    self.block();
    
    // Do any additional setup after loading the view.
}
-(void)dealloc{
    NSLog(@"%s",__func__);
}

这边vc 引用了block ,block引用了vc,最后面造成了循环引用,dealloc无法执行,那么如何解决呢?

方案一:通过weak来解决

使用weakself当vc退出是dealloc正常执行,但是也存在其他问题

-(void)test_dispatch_time{
    //循环引用
    self.name = @"block";
    //直接使用weakself来代替self,weakself是弱引用,这样不会导致引用计数+1。
    __weak typeof(self) weakself = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakself.name);
        });
    };
    self.block();
}

如果当我们延迟使用weakself的话,这时候的weakself可能已经被销毁了,这时候就需要用到__strong typeof(weakself) strongself = weakself; 

 //循环引用
    self.name = @"blockname";
    //直接使用weakself来代替self,weakself是弱引用,这样不会导致引用计数+1。
    __weak typeof(self) weakself = self;
    self.block = ^{
        __strong typeof (weakself) strongSelf = weakself;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });
    };
    self.block();

__strong可以让对象暂时在存活一段时间,用完就会销毁,这样也不会带来内存泄漏。
以上就是通过__weak__strong来解决block的循环引用。

方案二:通过临时变量来解决 

    self.name = @"block测试";
    __block BlockViewController *vc = self;
      self.block = ^(void){
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
              NSLog(@"%@",vc.name);
              vc = nil;
          });
      };
      self.block();

定义一个跟self一样类型的vc = self,然后block引用vc,用完之后再把vc=nil,这样引用链:self -> block -> vc -> self这样也可以解决循环引用问题 

方案三:通过参数将self传进去,传参的话,参数是在栈区,函数运行好栈区销毁参数也就跟着销毁,所以也可以解决循环引用问题

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

以上就是目前掌握的三种解决循环引用的方案。

Block的本质

接下来看看block通过xcrun后究竟是什么?

int main(int argc, char * argv[]) {
    int a = 9;
    __block int b = 10;
    NSObject *objc = [NSObject alloc];
    void(^Block)(void) = ^{
        NSLog(@"a:%d", a);
        NSLog(@"b:%d", b);
        NSLog(@"objc:%@", objc);
    };
    return 0;
}

xcrun -sdk iphonesimulator clang -rewrite-objc main.m
int main(int argc, char * argv[]) {
    int a = 9;
    __attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 10};

    NSObject *objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));

    void(*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, objc, (__Block_byref_b_0 *)&b, 570425344));
    return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  NSObject *objc;
  __Block_byref_b_0 *b; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSObject *_objc, __Block_byref_b_0 *_b, int flags=0) : a(_a), objc(_objc), b(_b->__forwarding) { // 构造函数
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 int b;
};

xcrun后看到的__block b,底层是变成了结构体b,里面保存了b的值10,整个block也变成了__main_block_impl_0结构体对象,把a, b, objc作为参数传进去。而在block结构体里定义了三个成员变量来保存a,b,objc


 

总结:

1:一共有三种类型的Block.分为__NSGlobalBlock,__NSStackBlock,__NSMallocBlock.
没有访问 auto变量 的block 就是 __NSGlobalBlock
访问了auto变量 的block 就是 __NSStackBlock
当一个__NSStackBlock调用了copy操作,返回的就是一个__NSMallocBlocksing
2:在ARC环境下,编译器会自动把栈上的block copy到堆上

3.block本质上也是一个oc对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。



 

猜你喜欢

转载自blog.csdn.net/wywinstonwy/article/details/125110549