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] ========<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

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

推荐阅读更多精彩内容