约搏三公开船:浅析Block闭包

admin 2周前 (05-21) 科技 6 0

浅析Block闭包

简朴来说,block就是将函数及其上下文封装起来的工具,从功效上可以把它看作是C++中的匿名函数,也可称之为块。

Block类型写法:

返回值+(^块名)+(参数)= ^(参数){ 内容 }

如下所示:

int (^myBlock)(int a, int b) = ^(int a, int b){
    return a + b;
};

Block结构

Block存储区域

Block本质上也是OC工具,以是每个Block工具也有isa指针指向它们的类工具。凭据Block类工具存储的内存空间的差别可分为三种差别的类,划分是:

位于全局区的Block类:__NSGlobalBlock__

位于栈区的Block类:__NSStackBlock__

位于堆区的Block类:__NSMallocBlock__

  • 全局区Block:当Block不捕捉外部变量时,会被编译器分配到全局区。由于无外部变量,以是运行时不会在Block内部举行copy或dispose操作,为了削减开销,以是在编译时就确定了巨细,即存储在全局区。如下:
void (^myBlock)(void)=^(void){
    NSLog(@"global");
};
NSLog(@"%@",[myBlock class]);

//输出:
//__NSGlobalBlock__
  • 栈区Block:当Block捕捉了外部变量后,会被分配到栈区。然则在ARC环境下,系统会自动为天生的栈区Block举行copy操作,以是为了验证是否是在栈区,需要接纳MRC环境,在main.m文件的编译选项设置为: -fno-objc-arc后运行如下代码:

    NSString* flag=@"yes";
    void (^myBlock)(void)=^(void){
        NSLog(@"stack:%@",flag);
    };
    NSLog(@"%@",[myBlock class]);
    
    //输出:
    //__NSStackBlock__
    
  • 堆区Block:在MRC模式下,用copy后,会将栈区block复制到堆区。在ARC模式下,系统自动将初始化的Block复制到堆区。

    //MRC环境下:
    NSString* flag=@"yes";
    void (^myBlock)(void)=[^(void){
        NSLog(@"stack:%@",flag);
    } copy];
    NSLog(@"%@",[myBlock class]);
    
    //输出:
    //__NSMallocBlock__
    

Block内部结构

官方的Block界说在 Block_private.h中,详细的源码:Block_private.h

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

//Block结构
struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
  • isa指针:指向类工具的指针,即凭据差别分区指向: __NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__,然则这里的底层isa现实上指向的是父类的结构体(C语言)即:_NSConcreteGlobalBlock _NSConcreteStackBlock _NSConcreteMallocBlock结构体,但意义是一样的。
  • flags:类型为枚举,主要用来保留Block的状态信息。
  • reserved:为之后开发准备的保留信息,暂时无用。
  • invoke:函数指针,指向的是现实的功效运行函数。在invoke函数的参数中还包含了Block结构体自己,这么做的目的是在执行时,可以从内存中获取block中捕捉的变量。
  • descriptor:主要存储Block的附加信息,其中包罗占址巨细、署名等。默认指向Block_descriptor_1结构体,当Block被copy到堆上时,则会添加Block_descriptor_2和Block_descriptor_3,新增copydispose方式用来拷贝和销毁捕捉的变量。

Block内部结构图(来自于Effective-OC):

Block作用

在一样平常的开发中,使用Block的主要用处在以下两个方面:

  1. 作为回调的方式之一,对比于署理模式,Block可将将涣散的代码块集中写在一处编写。由于有捕捉变量的机制,以是可以很轻松的接见上下文,而且Block的代码是内联的,运行效率会更高。

  2. 正是由于有了以上的优势,以是在编写异步代码,作为异步处置回调时,在封装时往往会接纳handler块的方式来编写相关代码。

    在编写handler块时有两种计谋,一种是在一个方式中提供提供两个Block块划分处置CompletionHandler和errorHandler,另外一种是只提供一个Block块,在Block块中提供error参数,用户自己来对error值举行判断。一样平常我们更倾向于后者的方式,由于这样处置数据会加倍天真

两种Handler气概如下:

Downloader *myDownloader = [[Downloader alloc] initWithURL:url];
[myDownloader downloadWithCompletionHandler:^(NSData *onlineData){
  //download success
}
failureHandler:^(NSError *error){
  //handle error
}];
Downloader *myDownloader = [[Downloader alloc] initWithURL:url];
[myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded){
  if(succeeded){
    //download success
  }
  else{
    //handle error
  }
}];

Block内存泄露

当几个oc工具相互强引用成环时,就会导致工具永远都不会被释放,当这些工具的数目很大时,就会造成内存泄露,从而导致整个系统crash的风险。

举个例子:

当A类工具强引用了B类工具,B类工具强引用了C类工具,而C工具又强引用了A类工具。假设它们都在一个代码段中。如下图所示:

由于a、b、c都被该代码段所强引用,以是retainCount初始化都为1,又由于它们相互强引用,以是在连成环的时刻retainCount都变为了2。这时刻在代码段中,无论是哪一个工具先从代码段中释放,即retainCount--,都仍然还剩1。当整个代码段执行完后,三个类工具a、b、c的retainCount都从2减为了1,在整个系统中,再也没有其他影响因素会让它们的retainCount削减为0,这样就会导致这三个工具在运行中永不释放,从而造成内存泄露。

在使用Block时也会很容易造成这个征象,当在网络异步的handler块中,我们通常会将当前ViewController中的某个网络数据属性捕捉到handler中,在网络毗邻乐成后将其举行赋值,这样就相当于Block块间接地强引用了当前VC,而通常来说,VC肯定会强引用下载器,而下载器中的Block块一样平常也会做为其属性举行强引用。如下图所示:

为了解决强引用环的问题,可以通过将随便一个毗邻处断开即可。

  • 断开1:基本不可能,在开发中在ViewController或者时ViewModel中都会将下载器作为属性而非暂且变量,由于在调取历程中会一样平常会凭据当前下载状态来举行下一步操作。

  • 断开2:

    方式一:不将_downloadHandler作为属性,而是使用暂且Block变量,通常这么做的情形是由于下载器类不需要多次使用该block,对于庞大的下载器,这种计谋很难得以保证。

    方式二:(推荐)在下载操作竣事后挪用的方式中令 self.downloadHandler = nil,只要下载请求执行完毕,_downloadHandler属性就不再强引用该block,就打破了强引用环。

  • 断开3:

    方式一:由于Block强引用了VC的data属性,现实上也就强引用了VC(self),以是我们可以通过: __weak typeof(self) weakSelf=self将当前VC,即self弱引用化,天生一个名为weakSelf的当前vc工具,然后在block中使用 weakSelf.data=_data来举行挪用。

    方式二:方式一中大部分情形不会泛起问题,然则当block块中有延时操作,而对_data的处置也在延时操作当中时,就会泛起问题了,例如:

    [self.myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded){
      if(succeeded){
        //download success
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //延迟2s获取data数据
            weakSelf.data = onlineData;
            NSLog(@"%@",weakSelf.data);
        });
      }
      else{
        //handle error
      }
    }];
    
    //假设乐成从网络上获取到data
    //打印为空
    

    这时刻就会发现,无论是weakSelf照样self的data属性都为空。这就是由于在block执行完后(延时函数还未执行完),weakSelf所在的弱引用表已经被除名了,虽然延时函数还在执行。这时刻当2s事后,weakSelf已经变为了nil,对nil发送getter新闻也不会报错,以是这里就会泛起取值为空的情形。

    为了解决这一问题,只需要在block内再将weakSelf在代码段内部强引用化(该强引用仅限于Block内部)。例如:

    [self.myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded){
      if(succeeded){
        //download success
        //将weakSelf强引用化天生该代码段的strong变量
        __strong typeof(self) strongSelf=weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //延迟2s获取data数据
            //这里使用strongSelf暂且变量
            strongSelf.data = onlineData;
            NSLog(@"%@",strongSelf.data);
        });
      }
      else{
        //handle error
      }
    }];
    

    这里的strongSelf属于暂且变量,会加到该代码段(Block内)的autoreleasepool当中,当该处代码段竣事时会自动释放掉,以是也就不会泛起强引用情形。

    方式三:使用暂且变量充当当前VC(self),如下:

    __block XXXViewController* vc = self;  //这里self的retainCount会+1
    [self.myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded){
      if(succeeded){
        //download success
        vc.data = onlineData;
        //这里需要注重将该暂且变量置为nil,即将retainCount重新减为1
        vc=nil;
      }
      else{
        //handle error
      }
    }];
    

    这里需要注重在赋完值后必须将该暂且变量重新置为nil,即将retainCount减1,否则仍会泛起强引用的问题。

    方式四:将当前self作为block参数传入,例如:

    [self.myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded, XXXViewController* vc){
      if(succeeded){
        //download success
        vc.data = onlineData;
      }
      else{
        //handle error
      }
    }];
    

    这种情形一样平常很少泛起,由于下载器通常作为第三方提供的API,通常参数不会有当前控制类。以是这种情形只能用在自界说block当中使用。

总结

  • 在ARC环境下开发,我们用到的一样平常都是堆Block或全局Block,当捕捉外界变量时为堆Block,否则为全局Block
  • Block主要用于代码回调以及异步操作以降低代码涣散水平。
  • Block在捕捉变量时很容易造成循环引用,导致内存泄露。在不确定挪用第三方API是否在最后将block属性置为空,或者没有使用属性而是暂且变量作为挪用block,以是在不破环封装性的原则下,将其视为未处置,然后在自己的代码中使用waekSelf和strongSelf方式来举行当前self的属性举行操作,这样就实现了在环节[3]中打破强引用环。
,

诚信阳光在线官网下载

诚信阳光在线官网(原诚信在线官网)现已开放阳光在线手机版、阳光在线电脑客户端下载。阳光在线娱乐游戏公平、公开、公正,用实力赢取信誉。

皇冠APP声明:该文看法仅代表作者自己,与本平台无关。转载请注明:约搏三公开船:浅析Block闭包

文章归档

站点信息

  • 文章总数:169
  • 页面总数:0
  • 分类总数:8
  • 标签总数:440
  • 评论总数:0
  • 浏览总数:314