在定义属性的时候,都需要为属性加特性,属性的特性决定了属性在原子性、读写权限以及内存管理的特性。
第一类:原子性(atomic,nonatomic)
(1)atomic(默认)
atomic意为操作是原子的,即是只能通过单个线程去访问实例变量。
如何去限制只有一个线程去访问实例变量?
这种特性下,由系统自动生成的setter/getter方法会加锁,但是即使这样也不能一定保证线程安全,而且比较耗时,所以我们一般不选用atomic。
(2)nonatomic
nonatomic正好与atomic相反,表示是多线程的,多个线程可以同时去访问实例变量。在这种特性下,系统自动生成的setter/getter方法不会上锁,所以我们一般选用这个特性。
第二类:读写权限(readwrite/readonly)
(1)用readwrite修饰的属性,拥有getter和setter方法
(2)用readonly修饰的属性,只拥有getter方法,没有setter方法。
第三类:内存管理
再介绍下面的特性之前,先了解一下“引用计数”。
(1) assign
assign主要应用于代表简单数据类型的property,比如int,float等。
如果这个用assign属性修饰的property代表一个指向对象的指针,那么当这个指针指向某个对象时,这个对象的引用计数不应该被改变。也就是说,用assign属性修饰的property,不应该持有一个对象。
(2)strong
strong强引用,当引用时,引用计数会加1,分配内存地址。
(3)weak
不会改变引用计数,weak比较常用的地方就是delegate属性的设置。
weak用于解决循环引用的例子
- delegate
例如:有两个类,分别是teacher和student,给teacher类添加个delegate的属性,并且让student作为teacher的代理。
//Teacher.h
#import <Foundation/Foundation.h>
@protocol TeacherDelegate <NSObject>
@end
@interface Teacher : NSObject
@property(nonatomic, weak) id <TeacherDelegate> delegate;
@end
//Teacher.m
#import "Teacher.h"
@implementation Teacher
- (void)dealloc {
NSLog(@"Teacher is dealloc");
}
@end
//Student.m
#import "Student.h"
#import "Teacher.h"
@interface Student ()<TeacherDelegate>
@property (nonatomic, strong) Teacher *teacher;
@end
@implementation Student
- (instancetype)init {
if (self = [super init]) {
self.teacher = [[Teacher alloc] init];
self.teacher.delegate = self;
}
return self;
}
- (void)dealloc {
NSLog(@"Student is dealloc");
}
@end
#import "ViewController.h"
#import "Student.h"
#import "Teacher.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc] init];
}
@end
打印结果:
如果将delegate的修饰符换为strong,则什么都打印不出来。原因是:
以上是strong和weak对于delegate修饰的区别,那weak和assign呢?
大多数情况下,修饰delegate,既可以用weak,又可以用assign。因为在几乎所有场景下,delegate所指向的对象B的生存期都是覆盖了delegate成员变量本身所在的对象A的生存期的,所以,在A的生存期内,B所使用的A的指针都是有效的,所以这个时候使用assign是没有关系的。
但是使用weak更加安全,因为如果在delegate所指向的对象B被释放掉,在使用delegate发送消息时,就会因assign修饰而没有将delegate及时置为nil导致delegate指向了一个已销毁的对象而crash;若使用weak则会及时将delegate置为nil,防止程序崩溃。例如下面例子:
//Teacher.h
#import <Foundation/Foundation.h>
@protocol TeacherDelegate <NSObject>
-(void)testDelegate:(NSString *)str;
@end
@interface Teacher : NSObject
@property(nonatomic, assign) id <TeacherDelegate> delegate;
- (void)test;
@end
//Teacher.m
#import "Teacher.h"
@implementation Teacher
-(void)test{
if(self.delegate && [self.delegate respondsToSelector:@selector(testDelegate:)]){
[self.delegate testDelegate:@"我是一条测试回调"];
}
}
@end
//ViewController.m
#import "ViewController.h"
#import "Student.h"
#import "Teacher.h"
@interface ViewController ()<TeacherDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
Teacher* teacher = [[Teacher alloc] init];
{
Student* student = [[Student alloc] init];
teacher.delegate = student;
}
[teacher test];
}
@end
所以修饰delegate时,我们既不选择strong也不选择assign;如果非要选择assign,在delegate所指的对象销毁的同时给delegate手动置nil。
- Block
当一个对象强引用一个block并且该block又强引用该对象,则会造成循环引用。例如:
//Teacher.h
#import <Foundation/Foundation.h>
typedef void (^MyBlock)(void);
@interface Teacher : NSObject
@property(nonatomic, strong) MyBlock block;
- (void)test;
@end
Teacher.m
#import "Teacher.h"
@implementation Teacher
-(void)test{
self.block = ^{
[self doSomething];
};
self.block();
}
- (void)doSomething {
NSLog(@"12312");
}
-(void)dealloc {
NSLog(@"已释放");
}
@end
#import "ViewController.h"
#import "Teacher.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
Teacher* teacher = [[Teacher alloc] init];
[teacher test];
}
@end
并不会调用dealloc方法,因为Teacher对象就没有释放。并且编译器会弹出警告信息:存在保留环,也就是循环引用。
想要避免这个问题可以有以下两种处理方式:
1. 将属性修饰符改为weak
2. 将block内的self改为弱引用weakSelf,`__weak typeof(self) weakSelf = self;`。
- NSTimer
在使用NSTimer时,VC会持有NSTimer,在创建NSTimer时会设置NSTimer的target,而这个target为self,就会形成VC->NSTimer、NSTimer->VC的循环引用。为了避免内存泄漏我们需要去解决循环引用。- 解决方案一:使用weak修饰符修饰NSTimer属性。(不可行)
@property(nonatomic, weak) NSTimer* timer;
根据打印信息可以看出,ViewController并没有释放。
- 解决方案二:将NSTimer的target 改为weakSelf。(不可行)
#import "ViewController.h"
#import "WeakObject.h"
@interface ViewController ()
@property(nonatomic, weak) NSTimer* timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:weakSelf selector:@selector(print) userInfo:nil repeats:NO];
}
- (void)print {
NSLog(@"%@", self.timer);
}
- (void)dealloc {
[_timer invalidate];
_timer = nil;
NSLog(@"%s", __func__);
}
根据打印结果,可以看出,这个方案也没有用。
- 解决方案三:重写一个继承于NSObject类的中间对象
VC强引用NSTimer,NSTimer强引用中间对象,中间对象弱引用VC。
#import "ViewController.h"
#import "WeakObject.h"
@interface ViewController ()
@property(nonatomic, strong) NSTimer* timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));
// 当前VC强引用了 timer
// timer 对 weakObject 强引用
// 但是 weakObject 对 self 是弱引用的关系
// 因此不会产生循环引用
WeakObject *weakObject = [WeakObject weakObjectWithTarget:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakObject selector:@selector(print) userInfo:nil repeats:NO];
NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));
}
- (void)print {
NSLog(@"%@", self.timer);
}
@end
- 解决方案四: 使用NSProxy类实现消息转发
.h
@interface Proxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end
.m
@interface Proxy()
@property (weak, nonatomic) id target;
@end
@implementation Proxy
+ (instancetype)proxyWithTarget:(id)target {
Proxy *proxy = [Proxy alloc];
proxy.target = target;
return proxy;
}
// 以下还是运用消息转发机制进行
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
-(void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
这个方法和上一个方法的不同是在于:寻找方法的顺序不同,上面是本类->父类->动态方法解析->消息转发;而这个是:本类->消息转发。可见这个方法可以提升性能。
- 解决方案五:在block内部使用弱引用
如果使用了iOS10之后的方法,就不用担心target引起的强引用了,但是要注意在block中避免循环引用。
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf doSomething];
}];
(4)copy
修饰NSString、NSArray、NSDictionary等有对应可变类型的对象,建立一个索引计数为1的对象,然后释放旧对象。所以被copy的对象引用计数不会加1。
(5)retain
retain不能修饰用来代表简单数据类型的property,如果一个property被retain修饰,这代表着这个property应该持有它所指向的对象。
retain和strong
- strong是对象的默认内存管理关键字,表明该属性定义了一种“持有关系”
- 声明属性时用strong或者retain效果是同样的(貌似更多开发者更倾向于用strong)。不过在声明Block时,使用strong和retain会有大相径庭的效果。strong会等于copy,而retain居然等于assign。
assign、weak和unsafe_unretain
- 相同之处:都不是强引用
- 不同之处:
- weak引用的OC对象被销毁时,指针会自动清空,不再只想销毁的对象,不会产生野指针错误;
- unsafe_unretain和assign修饰对象时在引用的OC对象被销毁时,指针不会清空。
- assign修饰基本数据类型,内存在栈上由系统自动回收,不会造成野指针的问题。
关于深拷贝和浅拷贝:
表一:
本对象类型 | copy/mutableCopy | 拷贝方式 | 生成对象类型 |
---|---|---|---|
不可变对象 | copy | 浅拷贝 | 不可变对象 |
不可变对象 | mutablCcopy | 深拷贝 | 可变对象 |
可变对象 | copy | 深拷贝 | 不可变对象 |
可变对象 | mutableCopy | 深拷贝 | 可变对象 |
表二:不可变对象在不同传入变量类型和不同修饰符下的拷贝方式:
传入变量类型 | 修饰符 | 拷贝方式 |
---|---|---|
不可变对象 | copy | 浅拷贝 |
不可变对象 | strong | 浅拷贝 |
可变对象 | copy | 深拷贝 |
可变对象 | strong | 浅拷贝 |
因为可变对象可以赋值给不可变对象,所以如果是strong修饰不可变对象时,内容会发生变化,而copy修饰符修饰的不可变对象会生成一个副本,原来的内容不会被修改。