[iOS] 内存管理相关修饰符

2019-08-04  本文已影响0人  木小易Ying

今天跑题跑回来看看内存相关的修饰符吧(strong, weak, assign, unsafe_unretained, retain)

首先先简要说下内存管理哈,简而言之就是iOS现在的ARC机制会给每个对象计算引用数,当引用数为0以后就会被dealloc啦;如果应该被销毁的对象由于引用不为0而无法被销毁,就是我们常见的内存泄漏。

① Strong

strong是我们日常最常见的,也是默认会给属性加上的,他和MRC下的retain其实作用一样,都是将赋值对象的引用数+1,将旧值引用数-1。

-(void) setName: (NSString *) name {
      if (_name != name) {
        [_name release];             //旧值的引用计数-1
        _name = [name retain];           //新值引用计数+1
     }
}

strong和retain都只能修饰OC对象,如果修饰NSIntegar这种基本数据类型会报编译错误哦~

strong不能修饰非OC对象

(但是MRC下用strong和retain修饰block是有区别的,具体可见参考文献。)

我们来尝试一下用strong来修饰Product的name属性~

@interface Product : NSObject
@property (nonatomic, strong) NSString *name;

- (void)printNameAddress;

@end

====================
NSString *sundayName = @"sunday";
Product *sunday = [[Product alloc] init];
sunday.name = sundayName;
NSLog(@"sundayName的对象地址%p, sunday.name的对象地址%p", sundayName, sunday.name);
NSLog(@"sundayName的对象指针地址%p", &sundayName);
[sunday printNameAddress]; //调用product自己去print &name
    
sundayName = nil;
NSLog(@"sundayName的对象指针地址%p", &sundayName);
[sunday printNameAddress];
NSLog(@"sundayName的对象地址%p, sunday.name的对象地址%p", sundayName, sunday.name);

输出:
sundayName的对象地址0x10d8233d8
sunday.name的对象地址0x10d8233d8 //指向相同

sundayName的对象指针地址0x7ffee23de8f0
sunday.name的对象指针地址0x60000148f9f8

//改为nil以后
sundayName的对象指针地址0x7ffee23de8f0
sunday.name的对象指针地址0x60000148f9f8 //地址未变

sundayName的对象地址0x0
sunday.name的对象地址0x10d8233d8 //指向不同了

当我们把sundayName赋值给sunday(prodcut).name的时候,是把@"sunday"的指针给了sunday(prodcut).name,所以@"sunday"的引用计数为2;当持有局部变量sundayName和sunday(prodcut)的代码块结束了,sundayName和sunday(prodcut)的引用计数就变成0了,就会被回收,@"sunday"的retain count也就变为0,也会被回收。

如果将sundayName设为nil,由于代码还没运行结束,sundayName还没有被销毁,只是不再指向@"sunday",但sunday(prodcut).name仍旧指向@"sunday",它的引用计数则变为1。


引用示意图

但strong不是万能的,错用有可能会造成循环引用,这个就是为什么delegate经常需要用weak,以及block块里面经常用weakSelf。

② Weak

weak是将属性指向赋值,但是赋值的retainCount不增加,当赋值的retainCount变为0以后即使有weak的属性指向它,它也会被销毁,weak属性会被置为nil。

据说是底层会自动维护一个weak属性map,当我们用weak修饰属性的时候,map内就会增加一个属性和值的键值对,当值被销毁以后,会遍历这个map,将对应的weak属性置为nil。

@property (nonatomic, weak) Product *smileProduct;

Product *smile1 = [[Product alloc] init];
self.smileProduct = smile1;
smile1 = nil;
NSLog(@"smileProduct:%@", _smileProduct);

输出:
smileProduct:(null)
====================

改为:
@property (nonatomic, strong) Product *smileProduct;
输出:
smileProduct:<Product: 0x600003ce7f20>

对比weak和strong的输出可以看出weak并不影响对象的引用计数,当对象引用归零销毁后,weak属性会为nil;但strong属性是会让对象引用加1的,所以只要有strong属性仍旧指向对象,该对象的retain count就不为0,也就不会被销毁。

一般IBOutlet、block、delegate里面经常会用weak,以及你不希望这个属性会影响对象销毁的时候,IBOutlet是因为通常我们将view拖入到.m文件里面的时候,其实这个view已经在nib里持有了,没有必要再strong持有一次了;delegate和block内之所以用weak其实大概率都是为了避免循环引用。


循环引用

这里以block为例说一下循环引用,这个是我最开始觉得很难理解的一个事儿,主要是智商不够用QAQ


block循环引用

当我们使用block块的时候,如果他不执行完就不会被销毁,例如循环执行的block就一直会存在,并被self持有,那么block的retain count就为1,不会被销毁;如果block以strong的形式持有self,那么只要block不销毁,self的retain count就不会为0也就不会被销毁,于是就形成了循环引用。

只要将block对self的持有变成weak,那么self的retain count就为0,当self销毁的时候,block由于持有者被销毁,它的retain count也就为0了,于是两者都可以被释放。

delegate之所以需要是weak其实也是这样,如果self持有view,那么view肯定不会被释放,如果view.delegate以strong的形式持有self,self就不能释放了。


delegate循环引用

③ Assign

assign和unsafe_unretained真的是一模一样。他们的作用和weak相似,就是指向但是引用数不变,但区别是如果属性指向的对象被释放,指针不会被置为nil,会出现野指针crash。

注意这里说的是对象哦,如果assign指向的是基本数据类型如NSIntegar,那么将有栈来管理,不会出现野指针

将上面的例子改为assign来尝试一下:

@property (nonatomic, assign) Product *smileProduct;

Product *smile1 = [[Product alloc] init];
self.smileProduct = smile1;
smile1 = nil;
NSLog(@"smileProduct:%@", _smileProduct);

代码运行到最后一行会crash,因为当smile1置为nil的时候,它所指向的product对象其实已经可以释放了,并没有强引用指向了,但是_smileProduct的指针没有被置空,当我们想要访问它的时候就会有野指针crash了。

Q: 那么赋值时对象的retain是否是在set方法里面做的呢?

- (void)setSmileProduct:(Product *)smileProduct {
    _smileProduct = smileProduct;
}

//会有warning
//Assigning retained object to unsafe_unretained variable; 
//object will be released after assignment
- (void)setSmileProduct:(Product *)smileProduct {
    _smileProduct = [smileProduct copy];
}

覆写了一下set方法,分别是直接赋值或者copy一下,然而再次运行发现还是crash了,所以对赋值对象的retain之类的其实是在set方法之外做的,无论你如何覆写都是不行的。

所以assign一般用来修饰基本的数据类型,包括基础数据类型 (NSInteger,CGFloat)和C数据类型(int, float, double, char, 等等),iOS中默认对基本类型的修饰符就是assign所以不用特殊加的哈

④ Retain

和strong是一样滴,跳过啦

⑤ Copy

上篇介绍了copy顺带探讨了一下权限和读写安全的修饰符,详见https://www.jianshu.com/p/1313aac306b1


大概是起床太早了还没彻底清醒,真是应了老郭的“心中不得宁静,清晨早做文章”了,最近都比较水-。- 下一篇可能是动画相关或者notification吧~ 希望自己变得越来越好,虽然也没什么意义,但有些事情,哪怕结果没有很好,尝试了就不后悔啦,七八年后的时候想起来可能会觉得傻但这就是青春吧,就和现在回忆以前一样,虽然后悔,但这就是已经走过的没有办法回头的岁月,只能keep going~ happy weekend~

Reference:

  1. 属性修饰符:https://www.jianshu.com/p/3cbc79424fb8
  2. retain和strong:https://www.jianshu.com/p/e2ccf2ae2b98
上一篇 下一篇

猜你喜欢

热点阅读