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

以下是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();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 1.1 局部变量 局部自动变量,在Block中可被读取。Block定义时copy变量的值,在Block中作为常量使...
    陈雨尘阅读 2,904评论 4 31
  • 「你的意思」 N讀經: 馬太福音26:39 他就稍往前走、俯伏在地、禱告說、我父阿、倘若可行、求你叫這杯離開我.然...
    NCNeverland阅读 299评论 0 0
  • 我们必须承认,如果我们不摘下 VR 头显,现在还是有很多事情是我们做不到的,甚至是给朋友打个电话或者打电话订个披萨...
    ty晌午之月阅读 306评论 0 0
  • 将数组相邻的两个元素比较,将小的数和大的数交换位置,否则不交换
    Veteor阅读 283评论 0 0
  • 北京梦之翼传统文化家塾 学子今日成长的力量 : 1,张嘉祺同学变化,越来越多了,吃饭的时候都能够心定一处,吃饭的速...
    梦之翼教育的简书阅读 233评论 0 0