写在前面
iOS属性的修饰符包括三个方面,读写权限(readonly/readwrite),线程安全(atomic/nonatomic),内存管理(assign、retain、copy、weak、strong)。这里主要简单介绍内存管理的修饰符。
内存管理
- 为什么要进行内存管理?由于移动设备的内存极其有限,所以每个APP所占的内存也是有限制的,当app所占用的内存较多时,系统就会发出内存警告,这时需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等。
- 内存管理的本质是什么?因为对象和其他数据类型在系统中的存储空间不一样,其它局部变量主要存放于栈中,而对象存储于堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄露。
举例说明:
在ARC下使用MRC,在工程的Build Phases的Compile Sources中选择需要使用MRC方式的.m文件,然后双击该文件在弹出的会话框中输入-fno-objc-arc
#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;
}
修饰对象容易出现内存泄漏,如图所示:
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