[iOS]Block技术中的weak-strong

作者: 肖浩呗 | 来源:发表于2015-09-11 15:34 被阅读6882次

    本篇文章的主要内容

    • 了解何谓block。

    • 了解block的使用方法。
      Block 是iOS在4.0版本之后新增的程序语法.

       在iOS SDK 4.0之后,Block几乎出现在所有新版的API之中,换句话说,如果不了解Block这个概念就无法使用SDK 4.0版本以后的新功能,因此虽然Block本身的语法有点难度,但为了使用iOS的新功能我们还是得硬着头皮去了解这个新的程序概念。
      

    一、看一看什么是Block

    我们使用^运算符来声明一个Block变量,而且在声明完一个Block变量后要像声明普通变量一样,最后要加;

    • 声明Block变量
      接下来,我们通过一个例子来声明一个简单的Block变量.
    int (^block)( int ) = NULL;
    

    我们一起来看一下声明Block变量的语法

    数据返回值类型  (^变量名) (参数列表) = NULL;
    
    • 赋值Block变量
    block = ^(int m){
       return m * m; 
    };
    
    • 使用Block变量
    //通过使用block变量,计算整型常量10的平方,并且打印在控制器输出
    NSLog(@"10的平方是:%d",block(10));
    

    注意,到目前我们应该有发现block变量的使用步骤,有类似于函数的步骤

    • 首先都要声明(声明函数,声明block变量);
    • 然后都要进行实现(实现函数,为block变量赋值实现过程);
    • 最后都要进行调用才能实现具体功能

    二、看一看如何直接使用block参数

    • 数组排序
        //声明数组变量
        NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithObjects:@"5",@"2",@"3",@"9",@"7", nil];
        
        //直接使用block进行数组升序排序
        [mutableArray sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
            //将两个参数转换为字符串的对象
            NSString *value1 = (NSString *)obj1;
            NSString *value2 = (NSString *)obj2;
            
            //value1与value2两个对象的比较结果直接返回
            return [value1 compare:value2];
        }];
        
        //打印可变数组变量
        NSLog(@"%@",mutableArray);
    
    • 简单的网络异步请求
        //声明网络地址对象
        NSURL *url = [NSURL URLWithString:@"http://www.qq.com"];
        
        //根据网络地址对象,声明网络请求对象
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        
        //直接使用block变量完成链接成功后的数据返回功能
        [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
            //将二进制数据使用utf8编码转换成字符串对象
            NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            
            //打印链接完成后的结果
            NSLog(@"%@",result);
            
        }];
    

    可以看出ios平台中的很多功能都已经集成了Block语法的处理方法.

    三、看一看深入理解Block语法

    在本节,主要要去介绍的就是使用__block修饰的变量能够完成的作用。

    先来看一个例子。

        //声明一个局部整型变量
        int intValue = 3;
        
        //声明一个返回值为int,一个int参数的block变量
        int (^block)(int) = ^(int m){
            return m * intValue;
        };
        
        //调用block变量,5作为参数之后的结果
        NSLog(@"block(5) = %d",block(5));
    

    在上面的例子中,我们将intValue变量称为block执行过程中的外部变量,在block执行过程中可以直接使用该外部变量。

    再看一个例子。

        //声明一个局部整型变量
        int intValue = 3;
        
        //声明一个返回值为int,一个int参数的block变量
        int (^block)(int) = ^(int m){
            intValue++;
            return m * intValue;
        };
        
        //调用block变量,5作为参数之后的结果
        NSLog(@"block(5) = %d",block(5));
    

    在上面的例子中,我们编译程序后发现编译器会有红色错误,错误提示为
    Variable is not assignable (missing __block type specifier)

    为什么会出现不能被赋值的错误提示呢?
    • block在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝
    • 在 block 块内使用该只读拷贝。

    那为了避免上述错误,就要使用__block修饰符来修饰外部变量,用来通知编译器该外部变量intValueblock中的intValue指的是同一块儿内存地址,而不需要内存拷贝。


    如下例:

        //将intValue局部整型变量使用__block修饰符进行修饰
        __block int intValue = 3;
        
        //声明一个返回值为int,一个int参数的block变量
        int (^block)(int) = ^(int m){
            intValue++;
            return m * intValue;
        };
        
        //调用block变量,5作为参数之后的结果
        NSLog(@"block(5) = %d",block(5));
    

    四、使用Block要注意的内存问题

    使用weak–strong dance技术来避免循环引用

    举例如下

    //
    //  ViewController.m
    //
    //  Created by lewis.
    //
    
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    {
        id observer;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        //添加观察者,观察主题修改消息通知,并且在收到消息通知后,打印视图控制器对象
        observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
            NSLog(@"%@",self);
        }];
    }
    
    //当视图控制器对象销毁时,移除观察者
    - (void)dealloc
    {
        if (observer) {
            [[NSNotificationCenter defaultCenter] removeObserver:observer];
        }
    }
    @end
    

    在上面代码中,我们添加向通知中心注册了一个观察者,然后在 dealloc 时解除该注册,一切看起来正常。但这里有两个问题:
    在消息通知 block 中引用到了 self,在这里 self 对象被 block 保留一次,而 observer 又 retain 该 block的一份拷贝,通知中心又持有 observer。因此只要 observer对象还没有被解除注册,block 就会一直被通知中心持有,从而 self 就不会被释放,其 dealloc 就不会被调用。而我们却又期望在 dealloc 中通过 removeObserver 来解除注册以消除通知中心对 observer/block 的 保留次数。
    同时,observer 是在 self 所在类中定义赋值,因此是被 self retain 的,这样就形成了循环引用。
    解决方式如下.

    //
    //  ViewController.m
    //
    //  Created by lewis.
    //
    
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    {
        id observer;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        //先声明一个weak弱对象
        __weak ViewController *wSelf = self;
        
        //添加观察者,观察主题修改消息通知,并且在收到消息通知后,打印视图控制器对象
        observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note){
            
            //在block的执行过程中,使用强对象对弱对象进行引用
            ViewController *bSelf = wSelf;
            if (bSelf) {
                NSLog(@"%@",bSelf);
            }
        }];
    }
    
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    //当视图控制器对象销毁时,移除观察者
    - (void)dealloc
    {
        if (observer) {
            [[NSNotificationCenter defaultCenter] removeObserver:observer];
        }
    }
    
    @end 
    
    • block 之前定义对 self 的一个弱引用 wSelf,因为是弱引用,所以当 self 被释放时 wSelf 会变为nil
    • block 中引用该弱应用,考虑到多线程情况,通过使用强引用 bSelf 来引用该弱引用,这时如果 self 不为 nil 就会 retain self,以防止在后面的使用过程中 self 被释放;
    • 在之后的 block 块中使用该强引用 bself,注意在使用前要对 bSelf 进行了 nil 检测,因为多线程环境下在用弱引用 wSelf 对强引用 bSelf 赋值时,弱引用 wSelf 可能已经为 nil 了。

    通过这种weak-strong手法,block 就不会持有 self 的引用,从而打破了循环引用。

    相关文章

      网友评论

      • iManuQiao:最近有个问题一直困扰我。如何防止我要用的值在进入block前被释放了?
      • 超_iOS://在block的执行过程中,使用强对象对弱对象进行引用
        ViewController *bSelf = wSelf;
        请问 if (bSelf) {
        NSLog(@"%@",bSelf);
        }
        为什么要加上判断,strong 不是会再block执行完后才释放么?
        fuadam1982:如果在执行block之前,wSelf就因为关联的self释放而变为nil了。那么bSelf也就是nil了。加上判断防止使用array、dictionary等出错。
        你理解的情况是在执行block时,wSelf还没有变为nil,那么确实在block执行完后才会导致self的释放。
        可以把判断放到block开始处,这样好理解一些。
        其实加不加__strong修饰逻辑上都说得通
      • 困惑困惑困惑:总结的不错
      • 羊羊羊的洋:给肖浩老师点个赞 虽然我是贞姐学生
        907f8161554b:@朱洋洋 :+1: 洋洋啊
      • YEE丽:多发些面试技巧😆😆😆
      • 无名氏_1:请教一个问题 我们在系统的animate 和 masonry 等第三方框架的 block 回调中 是否是不需要将 self 置于weak?
        肖浩呗:@姚宁 这段代码是不需要使用weak进行修饰,因为以下的这段Block变量
        ```
        ^(MASConstraintMaker *make) {
        make.left.top.mas_equalTo(0);
        make.width.equalTo(self.canvasScrollView.mas_width);
        make.height.mas_equalTo(scrollviewDefaultContentHeight);
        }
        ```
        并没有被任何对象持有,Block变量指针指向的栈区地址,变量被调用后就会释放掉,所以不存在循环持有的问题。
        但是如果有以下代码
        ```
        typedef void(^CanvasConstraintBlock)(MASConstraintMaker *make);
        @property (nonatomic,string) CanvasConstraintBlock ccb;

        self.ccb = ^(MASConstraintMaker *make) {
        make.left.top.mas_equalTo(0);
        make.width.equalTo(self.canvasScrollView.mas_width);
        make.height.mas_equalTo(scrollviewDefaultContentHeight);
        }];
        ```
        上述代码出现的问题就是,循环持有的问题了。
        赋值时,需如下:
        ```
        __weak VC wSelf = self;
        elf.ccb = ^(MASConstraintMaker *make) {
        //预防线程bug
        VC bSelf = wSelf;
        make.left.top.mas_equalTo(0);
        make.width.equalTo(bSelf.canvasScrollView.mas_width);
        make.height.mas_equalTo(scrollviewDefaultContentHeight);
        }];
        ```
        无名氏_1:贴一段具体代码
        [self.canvasContainer mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.mas_equalTo(0);
        make.width.equalTo(self.canvasScrollView.mas_width);
        make.height.mas_equalTo(scrollviewDefaultContentHeight);
        }];
        在masonry的约束block中 使用了self 这时候需要使用__block 修饰吗?
        我一般是没有进行修饰的 运行起来 也没啥问题 delloc 也能执行到
        不理解的是 masonry 约束block中的self 是masonry 帮我们做了weak修饰 还是本身就不需要weak 修饰 :pray:
        肖浩呗:@姚宁 还是要根据block变量与block变量过程内的self对象是否互相持有进行判断。
        具体分析类似'四、使用Block要注意的内存问题'中的举例 :smile:
      • bc3d3e66fba3:bu cuo
        肖浩呗:@bc3d3e66fba3 :relieved:
      • 匿名类:总结的不错
        肖浩呗:@匿名类 :smile:

      本文标题:[iOS]Block技术中的weak-strong

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