美文网首页
iOS属性的修饰符(assign、retain、copy、wea

iOS属性的修饰符(assign、retain、copy、wea

作者: 小宝二代 | 来源:发表于2019-08-06 12:07 被阅读0次

    写在前面

    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] ========<Person: 0x600000014fa0>
    
    #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] ========<Person: 0x600000014fa0>
    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: <NSObject: 0x600002d9c920>
    2019-08-06 11:10:53.480737+0800 Strong[5185:301402] B: (null)
    

    weak解决循环引用的问题

    // Test.h文件
    #import <Foundation/Foundation.h>
    
    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、wea

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