内存管理语义

MRC

1. assign

MRC 下 assign 为属性的默认修饰符,无论是简单的数据类型,还是指向对象的指针。

@property (nonatomic) NSString *name;

等价于:

@property (nonatomic, assign) NSString *name;

assign 主要用于修饰数据类型,数据类型变量的内存由编译器自动管理。比如 NSInteger、CGFloat 等。

assign 修饰对象属性时,其指向一个对象之后,不改变该对象的引用计数。即只引用已创建的对象,而不持有对象。

assign 修饰的属性不持有对象,当其指向的对象在别处释放后,该指针变为悬挂指针也叫野指针。

2. retain

retain 修饰的属性会持有它所指向的对象,对象的引用计数 +1,当不再需要使用该对象时需调用 release 释放。

@property 修饰的变量会自动合成 getter 和 setter 方法,手动调用 setter 方法时需保留新值再释放旧值。

3. copy

只能用于修饰对象属性,将对象赋值给 copy 属性时,一般情况下属性会持有该对象的一份拷贝。

NSObject 虽然声明了 copy 方法,但没有实现。自定义的对象需实现 NSCopying 协议的 copyWithZone: 方法才能实现拷贝。

copy 分为深拷贝和浅拷贝。对于 Foundation 中含有可变版本的对象类型,对其不可变版本的 copy 为浅拷贝,对于可变对象的 copy 为深拷贝。

  • 不可变对象 NSArray:
@property (nonatomic, copy) NSArray *imArray;

测试示例:

NSArray *iArray = @[@"Jone"];
p.imArray = iArray;

查看 iArrayp.imArray 地址:

(lldb) p iArray
(__NSArrayI *) $0 = 0x0000000100204170 @"1 element"
(lldb) p p.imArray
(__NSArrayI *) $1 = 0x0000000100204170 @"1 element"

可以看出对于 NSArray 对象的拷贝为浅拷贝,它们仍然指向同一个内存地址。

  • 可变对象 NSMutableArray
@property (nonatomic, copy) NSMutableArray *mArray;

测试示例:

NSMutableArray *mArray = [[NSMutableArray alloc] initWithObjects:@"Jone", nil];
p.mArray = mArray;

查看 mArrayp.mArray 地址:

(lldb) p mArray
(__NSArrayM *) $0 = 0x0000000100300260 @"1 element"
(lldb) p p.mArray
(__NSArrayI *) $1 = 0x0000000100203a40 @"1 element"

可变对象 NSMutableArray 的拷贝为深拷贝,拷贝后指向不同的内存地址。
虽然此时 p.mArraymArray 代表不同的对象,但是此刻手痒打印出其元素的地址如下:

(lldb) p mArray[0]
(__NSCFConstantString *) $2 = 0x0000000100002108 @"Jone"
(lldb) p p.mArray[0]
(__NSCFConstantString *) $3 = 0x0000000100002108 @"Jone"

虽然对可变对象的 copy 为深拷贝,但其内容相同的元素,仍然是对同一个对象的引用。

在使用含有可变版本的对象类型时,其不可变版本需用 copy 修饰符,比如: NSString、 NSArray 等,防止外部将可变版本的对象赋值给该对象属性,然后该可变对象又被意外修改,会造成该属性的内容也被修改。

@property (nonatomic, retain) NSArray *imArray; // 不可变对象推荐使用 copy
NSMutableArray *mArray = [[NSMutableArray alloc] initWithObjects:@"Jone", nil];
p.imArray = mArray;
[mArray addObject:@"iOSTalk"]; // 此时 p.imArray 被修饰,需使用 copy 修饰 imArray。
(lldb) po p.imArray
<__NSArrayM 0x1007005c0>(
Jone,
iOSTalk
)

在使用可变对象作为属性时,慎用 copy 修饰符。如果将可变对象赋值给该属性,该属性持有的对象将会是不可变版本的,如果再对其执行可变版本的方法,将会造成 crash。

@property (nonatomic, copy) NSMutableArray *mArray;// 可变对象慎用 copy
NSMutableArray *mArray = [[NSMutableArray alloc] initWithObjects:@"Jone", nil];
p.mArray = mArray;
[p.mArray addObject:@"iOSTalk"]; // Crash

4. unsafe_unretained

语义等同于 assign,一般用于修饰对象,也可用于修饰数据类型。

ARC

当 ARC 有效时,以下属性可用于属性声明:

属性声明的属性 所有权修饰符
assign __unsafe_unretain 修饰符
copy __strong 修饰符(赋值的是被复制的对象)
retain __strong 修饰符
strong __strong 修饰符
unsafe_unretain __unsafe_unretained 修饰符
weak __weak 修饰符

1. assign/copy/retain

同 MRC 下的语义。

2. strong

strong 为 ARC 下属性的默认内存管理语义,语义等同于 retain。变量的所有权修饰符为 __strong

被赋值时会持有对象,阻止对象被释放。

在 YYMemoryCache 中有这么一段代码:

__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        [self _trimInBackground];
        [self _trimRecursively];
    });

这段代码先是使用 _self弱引用 self 再在 block 中使用 __strong 修饰符强引用 self。使用 _self 是为了防止引用循环。而在 block 中使用强类型的 self 是为了防止 YYMemoryCache 实例被释放后,已经提交至队列执行的 block 中 _selfnil

3. weak

weak 修饰符弱引用对象,不改变对象的引用计数,当其指向的对象被销毁时,它会自动的置为 nil。变量权限修饰符为 __weak,常用于容易造成循环引用的地方。

__weak 实现原理:将 __weak 指向的对象地址作为键值,__weak 指针变量的地址注册到 weak 表。

由于注册、以及将 weak 变量置为 nil 有一定的开销,如果大量的使用 weak 变量,会消耗一定的 CPU 资源。

4. unsafe_unretain

unsafe_unretain 语义等同于 assgin ,ARC 下编译器不负责管理其修饰属性的内存。功能类似于 weak 但唯一不同的区别在于,weak 所指向的对象被销毁后会被赋值为 nil,而 unsafe_unretain 修饰的属性变量成为悬挂指针。

由于 __weak__unsafe_unretain 都不持有对象,如下两行代码都有引起编译器警告。

id __weak obj = [[NSObject alloc] init];
id __unsafe_unretain obj = [[NSObject alloc] init];

__unsafe_unretain 可在结构体中修饰对象, 而其他修饰符都不可以。

struct stringAndInt {
    NSString * __unsafe_unretained s;
    int x;
}

你可能感兴趣的:(内存管理语义)