iOS属性的修饰符(assign、retain、copy、weak、strong)

写在前面

iOS属性的修饰符包括三个方面,读写权限(readonly/readwrite),线程安全(atomic/nonatomic),内存管理(assign、retain、copy、weak、strong)。这里主要简单介绍内存管理的修饰符。

内存管理

  1. 为什么要进行内存管理?由于移动设备的内存极其有限,所以每个APP所占的内存也是有限制的,当app所占用的内存较多时,系统就会发出内存警告,这时需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等。
  2. 内存管理的本质是什么?因为对象和其他数据类型在系统中的存储空间不一样,其它局部变量主要存放于栈中,而对象存储于堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄露。

举例说明:
在ARC下使用MRC,在工程的Build Phases的Compile Sources中选择需要使用MRC方式的.m文件,然后双击该文件在弹出的会话框中输入-fno-objc-arc

MRC

#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@end

@implementation ViewController

//在viewDidLoad创建一个person对象
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib
    
    Person *person = [[Person alloc] init];
    NSLog(@"========%@",person);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

#import "Person.h"

@implementation Person
//dealloc方法没有调用,说明person对象没有被释放
- (void)dealloc {

    NSLog(@"被调用了。。。。。");
}

@end

//打印结果
2019-06-17 13:05:48.833884+0800 MRC[7412:3136111] ========
#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib
    
    Person *person = [[Person alloc] init];
    NSLog(@"========%@",person);
    //手动释放person对象
    [person release];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

#import "Person.h"

@implementation Person


- (void)dealloc {

    NSLog(@"被调用了。。。。。");
}

@end

//打印结果
2019-06-17 13:05:48.833884+0800 MRC[7412:3136111] ========
2019-06-17 13:05:48.834015+0800 MRC[7412:3136111] 被调用了。。。。。

当viewDidLoad代码块结束时,指向person对象的指针被回收,然而存放在堆区的person对象需要手动释放,从而可以看出存放在堆区的对象需要进行内存管理的,而存放在栈区的基本数据类型、局部变量等系统自动管理。

苹果对内存管理可以分为两个阶段,第一阶段是MRC,需要程序员手动创建手动释放,比如上面例子,这一阶段属性的修饰词为assign、retain、copy。第二阶段是ARC,编译器自动进行内存管理,这一阶段属性的修饰词为weak、strong,接下来就介绍一下这几个修饰词。


assign

不会使引用计数加1,直接赋值,可修饰对象,和基本数据类型。当需要修饰对象类型时,MRC时代使用unsafe_unretained。当然,unsafe_unretained也可能产生野指针,所以它名字是"unsafe_”。所以一般用它来修饰基本数据类型,不用它修饰对象。

// setter方法直接赋值
-(void)setAge:(int)age {
    _age = age;
}

修饰对象容易出现内存泄漏,如图所示:


assgin修饰对象内存泄漏.png

retain

会使引用计数加1,ARC下已经不再使用,用strong代替

// setter方法释放旧对象,retain新对象
@property (nonatomic, retain) NSString *name;
- (void)setName:(NSString *)name
{
    if (_name != name) {
        [_name release];
        _name = [name retain];
     }  
}

copy

建立一个索引计数为1的对象,在赋值的时使用传入值的一份拷贝,适用于NSString和block

// setter方法释放旧对象,copy新对象
@property(nonatomic, copy) NSString *name;
- (void)setName:(NSString *)name
{
    if (_name != name) {
        [_name release];
        _name = [name copy];
     }
}

至于为什么适用于NSString请参考我的另一篇文章iOS深拷贝和浅拷贝,block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区。在 ARC 中对于 block 使用 copy 还是 strong 效果是一样的,如果不写 copy ,该类的调用者有可能会忘记或者根本不知道编译器会自动对 block 进行了 copy 操作,他们有可能会在调用之前自行拷贝属性值。

weak

不增加引用计数,也不持有对象,ARC时才会使用,ARC模式下会使用,相当于assign,对象废弃可以把对应的指针变量置为nil的状态。只可以修饰对象,如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”
weak使指针变量置为nil

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, weak) NSObject *obj1;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.obj1 = nil;
    {
        // 指针变量obj0持有对象的强引用
        id obj0 = [[NSObject alloc] init];
        // 指针变量obj1持有对象的弱引用
        self.obj1 = obj0;
        
        // 输出obj1变量持有的弱引用的对象
        NSLog(@"A: %@",self.obj1);
    }
    /*
     * 因为obj0变量超出其作用域,强引用失效
     * 所以自动释放自己持有的对象
     * 因为对象无持有者,所以废弃该对象
     *
     * 废弃对象的同时
     * 持有该对象弱引用的obj1变量的弱引用失效,nil赋值给obj1
     */
    
    NSLog(@"B: %@",self.obj1);
    
    /*
     * 输出赋值给obj1变量中的nil
     */
}

// 打印结果
2019-08-06 11:10:53.480572+0800 Strong[5185:301402] A: 
2019-08-06 11:10:53.480737+0800 Strong[5185:301402] B: (null)

weak解决循环引用的问题

// Test.h文件
#import 

NS_ASSUME_NONNULL_BEGIN

@interface Test : NSObject

// 这里我们先使用strong修饰,看一下会出现什么问题
@property (nonatomic, strong) NSObject *obj;

@end

NS_ASSUME_NONNULL_END

// Test.m文件
#import "Test.h"

@implementation Test

- (void)dealloc {
    
    NSLog(@"对象已废弃。。。。。");
}

@end

// ViewController文件
#import "ViewController.h"
#import "Test.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    // test0持有Test对象A的强引用
    Test *test0 = [[Test alloc] init];
    // test1持有Test对象B的强引用
    Test *test1 = [[Test alloc] init];
    
    /*
     * Test对象A的_obj成员变量持有Test对象B的强引用
     *
     * 此时,持有Test对象B的强引用的变量为
     * Test对象A的_obj和test1
     */
    test0.obj = test1;
    
    /*
     * Test对象B的_obj成员变量持有Test对象A的强引用
     *
     * 此时,持有Test对象A的强引用的变量为
     * Test对象AB的_obj和test10
     */
    test1.obj = test0;
}

/*
 * 因为test0变量超出其作用域,强引用失效,
 * 所以自动释放Test对象A.
 *
 * 因为test1变量超出其作用域,强引用失效,
 * 所以自动释放Test对象B.
 *
 * 此时,持有Test对象A的强引用的变量为
 * Test对象B的_obj
 *
 * 此时,持有Test对象B的强引用的变量为
 * Test对象A的_obj
 *
 * Test对象A和Test对象B没有被废弃
 * 发生内存泄漏
 */

@end

// - (void)dealloc 未调用,无打印结果

如果把@property (nonatomic, strong) NSObject *obj;中strong换成weak,该现象便可避免。
在ARC中有可能会出现循环引用的情况,往往通过其中一端使用weak来解决, 比如delagate代理属性,自身已经对它有过一次强应用,没有必要再强引用一次,这个时候也会使用weak。

strong

会使引用计数加1,ARC时才会使用,相当于retain。ARC 下不显式指定任何属性关键字时,基本数据默认的关键字是 atomic、readwrite、assign,普通的 OC 对象: atomic、readwrite、strong
至于NSMutableString为什么会用strong修饰可以参考我的另一篇文章iOS深拷贝和浅拷贝

写在最后

由于技术水平有限,若有错误之处欢迎留言指正,不胜感激。

参考链接
https://www.cnblogs.com/wendingding/p/3704739.html
https://www.jianshu.com/p/af4edb0e6701

你可能感兴趣的:(iOS属性的修饰符(assign、retain、copy、weak、strong))