美文网首页iOS开发进阶A原理/底层程序员
iOS开发中weak和strong的用法和错误示例

iOS开发中weak和strong的用法和错误示例

作者: 不上火喝纯净水 | 来源:发表于2016-08-28 15:51 被阅读2144次

    以下是ViewController中的两个方法,大家觉得有没有问题呢?

    - (void)removeRect {
        @weakify(self);
       //动画执行三秒后删除视图
        [UIView animateWithDuration:3 animations:^{
            @strongify(self);
            self->_rect.center = CGPointMake([UIScreen mainScreen].bounds.size.width + self->_rect.center.x, self->_rect.center.y);
        } completion:^(BOOL finished) {
            @strongify(self);
            [self->_rect removeFromSuperview];
            self->_rect = nil;
        }];
    }
    
    - (void)testPrint2 {
        if (!self->_printBlock) {
            @weakify(self);
            self->_printBlock = ^{
                @strongify(self);
                NSLog(@"self.name = %@", _name);
                NSLog(@"self.title = %@", self.title);
            };
        }
        self->_printBlock();
    }
    

    没错编译的时候不会出错误,但是运行的时候第一个方法可能会导致crash,第二个方法会导致循环引用

    大家在iOS开发中无可避免的会使用到weak和strong这两个关键字,说的简单就是弱引用和强引用,但是当你没有真正搞清楚怎么用的时候就可能在使用中有一些困扰,本篇文章着重讲述weak和strong的使用场景和基本原理,至于oc底层的语言级别的实现原理这里不作阐述(底层原理请自行度娘),请注意文中的代码都是ARC模式下的,大伙儿和大神们如果有更多的见解欢迎拍砖回复。

    一. weak和strong语法使用

    1.使用weak在定义属性的地方修饰属性,表示对该属性赋值(obj.property1=newValue)的时候不会对newValue进行retain,引用计数不会增加,并且当所引用的对象newValue=nil的时候该属性也将自动置为nil。

    @property (nonatomic, weak) NSString *property1;
    

    2.使用strong在定义属性(object type)的地方修饰属性,表示对该属性赋值(obj.property2=newValue)的时候会对newValue进行retain,引用计数增加1。对于对象类型(object type)的属性,strong是默认的缺省值。

    @property (nonatomic, strong) NSString *property2; 
    // 等价于 @property (nonatomic) NSString *property2;
    

    3.使用__weak在定义变量的时候用来修饰变量,表明对该变量进行赋值(tempName1 = newValue)的时候不会对newValue进行retain,引用计数不会增加,并且当所引用的对象newValue=nil的时候该变量也将自动置为nil。

    __weak NSString *tempName1;
    tempName1 = newValue;
    

    4.使用\__strong在定义变量(object type)的时候用来修饰变量,表明对该变量进行赋值(tempName1 = newValue)的时候会对newValue进行retain,引用计数会增加1。对于对象类型(object type)的变量,__strong是默认的缺省值。

    __strong NSString *tempName2; // 等价于 NSString *tempName2;
    tempName2 = newValue;
    

    二. 在开发中weak和strong使用的场景

    1. 使用weak打破属性的循环引用

    在2个对象相互之间的属性可能相互是对方的话,可能会引发循环引用而导致这2个对象都不能被释放,这时候只要把其中一个对象的属性用weak修饰即可打破可能的循环引用;

    使用场景a:两个对象有从属关系,例如班级和学生,班级拥有学生的引用,学生拥有班级的引用;

    使用场景b:两个对象是代理和被代理的关系,iOS开发中的代理模式就是如此,例如UITableView类中的代理属性

    @property (nonatomic, weak, nullable) id<UITableViewDataSource> dataSource;
    @property (nonatomic, weak, nullable) id<UITableViewDelegate> delegate;
    
    2. 使用__weak打破block的循环引用

    在调用block的时候是采用copy的方式,所以block的外部变量都会被copy,所以引用计数会+1。如果一个对象object对某个block持有引用,并且这个block中又使用了这个object那么就会造成循环引用,就会导致这个对象无法释放,这时候只需要在block外部重新定义一个采用__weak修饰的变量,这个变量指向object,然后在block里面使用这个新定义的变量即可。

    - (void)testPrint1 {
        if (!self->_printBlock) {
            self->_printBlock = ^{
                NSLog(@"self.name = %@", self.name);
            };
        }
        self->_printBlock();
    }
    
    - (void)testPrint2 {
        if (!self->_printBlock) {
            __weak typeof(self) wself = self;
            self->_printBlock = ^{
                NSLog(@"self.name = %@", wself.name);
            };
        }
        self->_printBlock();
    }
    

    上述方法testPrint1会引起循环引用,testPrint2的block中使用了__weak修饰的变量wself所以不会出现循环引用;

    3. 在block中使用\__strong用来保证\外部__weak的一致性

    block是objc语言一个很棒的特性,在实际的开发当中block的使用场景会很多也可能会很复杂,有一点要关注的是,虽然我们使用\__weak解决了循环引用的问题,但也正是因为使用了\__weak所以这个\__weak变量可能随时被释放掉,如果block中多次使用__weak,就有可能出现一开始有值,中途\__weak变量被释放,导致后续使用\__weak变量会出错,这时候可以在block内部最开始的地方定义一个\__strong的变量,这个\__strong变量指向外部定义的\__weak变量;

    - (void)testPrint3 {
    
        if (!self->_printBlock) {
            __weak typeof(self) wself = self;
            self->_printBlock = ^{
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    wself.name = @"testPrint3";
                    NSLog(@"self.name = %@", wself.name);
                    [NSThread sleepForTimeInterval:10];
                    NSLog(@"self.name = %@", wself.name);
                });
            };
        }
    
        self->_printBlock();
    }
    
    - (void)testPrint4 {
        
        if (!self->_printBlock) {
            __weak typeof(self) wself = self;
            self->_printBlock = ^{
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    __strong typeof(wself) sself = wself;
                    sself.name = @"testPrint4";
                    NSLog(@"self.name = %@", sself.name);
                    [NSThread sleepForTimeInterval:10];
                    NSLog(@"self.name = %@", sself.name);
                });
            };
        }
        
        self->_printBlock();
    }
    

    当testPrint3执行以后,会立即打印出self.name = testPrint3,此时如果self如果释放,第二句打印将会是self.name = (null);而执行testPrint4则不会,它会等block执行完以后才释放,里面的那一句__strong typeof(wself) sself = self;保证了整个block中一直被强引用而不会释放;

    所以很多情况下我们使用block的时候都会采用\__weak和\ __strong成对出现,因为使用的比较多所以RAC库中作者为我们准备了一个宏定义,在库中的RACEXTScope.h头文件中,我们可以以最快的方式书写上述语句,而且支持多个对象;

    上面代码可以改写为如下:

            @weakify(self);
            self->_printBlock = ^{
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    @strongify(self);
                    self.name = @"testPrint4";
                    NSLog(@"self.name = %@", self.name);
                    [NSThread sleepForTimeInterval:10];
                    NSLog(@"self.name = %@", self.name);
                });
            };
    

    三. 容易出现的错误或者被忽视的使用方式

    ~

    1. 滥用\__weak+\__strong

    ~
    请看下面代码:

    - (void)removeRect {
        
        @weakify(self);
        [UIView animateWithDuration:3 animations:^{
            @strongify(self);
            self->_rect.center = CGPointMake([UIScreen mainScreen].bounds.size.width + self->_rect.center.x, self->_rect.center.y);
        } completion:^(BOOL finished) {
            @strongify(self);
            [self->_rect removeFromSuperview];
            self->_rect = nil;
        }];
    }
    

    点击界面中的一个按钮,会让界面中的一个rect做动画,这时候让界面返回上一级,该动画立即会终止,并执行completion的block,但是这时候self指针已经为nil,调用一个nil指针的成员变量会怎样,会EXC_BAD_ACCESS,(当然把代码中的self->_rect改为self.rect就不会报错了)

    所以总结起来:\__weak+\__strong虽然可以应对大多数情况(看起来是万能的呀),但是我们每次使用block的时候还是得自己注意,不要一股脑都写上\__weak+\__strong,需要使用的时候才使用,有很多情况下本身就是需要block中强引用外部变量,以达到block执行完之后才可以去释放外部变量的目的,这时候你使用\__weak+\__strong反而是多余的。

    2. 使用@weakify(self)和@strongify(self)宏的时候block中使用类成员变量_var前面没有使用self->前缀

    ~
    使用RAC这对宏确实可以省去不少编码时间,但是RAC的这对宏对block中类似“_var.age = 88"的代码并不起作用,因为@strongify(self)宏仅仅是在block内部定义了一个和self一样的名称的变量,所以如果没有写成self->_var这种样式,编译器应该会认为是外部的self指针,从而对外部self有强引用,极易导致让你莫名的循环引用;

    - (void)testPrint2 {
        if (!self->_printBlock) {
            @weakify(self);
            self->_printBlock = ^{
                @strongify(self);
                // 会引起循环引用,将_name 改为 self->_name即可;
                NSLog(@"self.name = %@", _name);
                NSLog(@"self.title = %@", self.title);
            };
        }
        self->_printBlock();
    }
    

    相关文章

      网友评论

      • 社会主义接班人1984:UIVIew的block块动画是否海需要进行__weak和__strong,这种类方法调用的block会有循环引用吗
        不上火喝纯净水:答案:不需要
        1. 类对象的生命周期同整个app一样,所以类对象在app运行期间不会被释放,类对象可以看做是一个单例;
        2. 对象会持有block的前提是对象内部使用属性或者成员变量来接收这个block,UIView的类对象并没有属性或者成员变量来接收这个block,block仅仅作为方法的参数存在于方法内部,所以UIView类对象没有持有这个block;
        3. 循环引用的发生条件是对象直接或间接持有block,且block内部持有该对象。
        4. __weak除了用来解决循环引用,也可以使用它来解除异步执行的block对对象的持有,使对象可以及时的释放,比如做一个网络请求,当界面返回之后我们希望界面能立即释放,就可以使用__weak了
      • 洁简:怎么判断weak后的self是weak类型呢
        不上火喝纯净水:引用计数。可以在这句代码之前和之后 打印出self的引用计数,weak 指针指向self后,self引用计数不会变化,而strong指针则会导致 引用计数+1. 使用CFGetRetainCount 获取引用计数。
        洁简:@不上火喝纯净水 嗯 我的意思是说 __weak typeof(self) wself = self 后怎么确定这就是__weak 而不是strong 能看出来吗 或者能打印出来哪里不同吗
        不上火喝纯净水:@洁简 没明白你的意思,weak类型的指针有可能会变成nil,所以有必要的话block中使用之前需要对该值做nil判断
      • hzy1314:楼主,你的在外部变量使用weak那一块错了吧,用weak修饰的属性指向局部强引用变量,出了作用域后立刻会被释放的
      • lxl125z:weak 为啥在对象释放掉后会变成 nil?
        不上火喝纯净水:这个是oc语言 weak修饰的变量的一个特性,试想如果释放之后不变成nil,你使用这个对象指针就会造成崩溃,即是野指针,所以苹果这么做是为了编程安全考虑的,否则你需要在释放的时候自己手动设置为nil。
      • Larrycal:谢谢博主,看Effecitive OC最后一条的时候,一直在纠结为什么要把strongSelf指向weakSelf呢?直接把[strongSelf p_doPoll]换成[weakSelf p_doPoll]可以吗?看了博文之后,瞬间明白了!

      本文标题:iOS开发中weak和strong的用法和错误示例

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