注册

Objective-C高级编程笔记一(自动引用计数)

示例代码下载

手动引用计数

MRC内存管理的思考方式

1、自己生成的对象自己持有
2、不是自己生成的对象,自己也能持有
3、不在需要自己持有的对象时释放
4、不是自己持有的对象无法释放

对象操作与Objective-C方法的对应

035f134119054497b0035959a57271d4.png

实现一个MRCObject类:

@implementation MRCObject
- (void)dealloc {
NSLog(@"%@(%@)销毁了", NSStringFromClass(self.class), self);

[super dealloc];
}
+ (instancetype)object {
MRCObject *obj = [self allocObject];
[obj autorelease];
return obj;
}

+ (instancetype)allocObject {
MRCObject *obj = [[MRCObject alloc] init];
NSLog(@"%@(%@)生成了", NSStringFromClass(obj.class), obj);

return obj;
}

@end

自己生成并持有对象:

MRCObject *obj = [MRCObject allocObject];

不是自己生成的对象也能持有:

MRCObject *obj = [MRCObject object];
[obj retain];

不在需要自己持有的对象时释放:

MRCObject *obj = [self allocObject];
[obj release];

无法释放自己没有持有的对象:

MRCObject *obj = [self allocObject];
[obj release];
[obj release];//会奔溃

autorelease

autorelease像c语言的自动变量来对待对象实例,当超出其作用域(相当于变量作用域),对象实例的release方法被调用。与c语言自动变量不同的是,可以autorelease的作用域。

autorelease的使用方法:

1、生成NSAutoreleasePool对象
2、调用已分配对象实例的autorelease方法
3、废弃NSAutoreleasePool对象

在应用程序中,由于主线程的NSRunloop对NSAutoreleasePool对象进行生成、持有和废弃处理。因此开发者不一定非得使用NSAutoreleasePool对象来进行开发工作。如下图:

62650e8ec87e7aa304207762f27cc2a8.png

在大量产生autorelease对象时,只要不废弃NSAutoreleasePool对象,autorelease对象就不会被释放,因此会产生内存不足的现象。如下两段代码:

for (int index = 0; index < 1000; index++) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"1553667540126" ofType:@"jpeg"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
[image autorelease];
}
for (int index = 0; index < 1000; index++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *path = [[NSBundle mainBundle] pathForResource:@"1553667540126" ofType:@"jpeg"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
[image autorelease];
[pool drain];
}

ARC

ARC规则

ARC有效时,id类型和对象类型同c语言其他类型不同,必须添加所有权修饰符。共如下4种所有权修饰符:

1、__strong修饰符
2、__weak修饰符
3、__unsafe_unretained修饰符
4、__outoreleasing修饰符

import "ARCObject.h"

实现一个ARCObject类:

@interface ARCObject ()
{
__strong id _strongObj;
__weak id _weakObj;
}

@end

@implementation ARCObject

- (void)dealloc {
NSLog(@"%@(%@)销毁了", NSStringFromClass(self.class), self);
}
+ (instancetype)allocObject {
ARCObject *obj = [[ARCObject alloc] init];
NSLog(@"%@(%@)生成了", NSStringFromClass(obj.class), obj);
return obj;
}
- (void)setStrongObject:(id)obj {
_strongObj = obj;
}
- (void)setWeakObject:(id)obj {
_weakObj = obj;
}

@end

__strong修饰符

__strong修饰符是所有id类型和对象类型默认的所有权修饰符,表示对对象的强引用,在超出其作用域或被重新赋值时被废弃。

{
ARCObject *obj = [ARCObject allocObject];
NSLog(@"作用域最后一行%@", obj);
}
NSLog(@"作用域已经结束");
ARCObject *obj = [ARCObject allocObject];
NSLog(@"重新赋值前%@", obj);
obj = [ARCObject allocObject];
NSLog(@"重新赋值前后%@", obj);

__strong、__weak、__outoreleasing修饰符的自动变量默认初始化为nil。

__weak修饰符

__weak修饰符与__strong修饰符相反,提供弱引用,弱引用不持有对象实例。

循环引用容易发生内存泄漏,内存泄漏就是应当废弃的对象在超出其生存周期后依然存在。可以使用__weak修饰符来避免。

ARCObject *aObj = [ARCObject allocObject];
ARCObject *bObj = [ARCObject allocObject];
[aObj setStrongObject:bObj];
[bObj setStrongObject:aObj];
ARCObject *obj = [ARCObject allocObject];
[obj setStrongObject:obj];
ARCObject *aObj = [ARCObject allocObject];
ARCObject *bObj = [ARCObject allocObject];
ARCObject *cObj = [ARCObject allocObject];
[aObj setWeakObject:bObj];
[bObj setWeakObject:aObj];
[cObj setWeakObject:cObj];

__weak修饰符有一个优点就是:在持有某对象的弱引用时,如果该对象被废弃,则该对象弱引用自动失效且被置为nil。

__unsafe_unretained修饰符

__unsafe_unretained修饰符,正如其名一样是不安全的所有权修饰符。尽管ARC的内存管理是编译器的工作,但是这一点需要注意特别注意,__unsafe_unretained修饰符的变量不属于编译器内存管理的对象。

__unsafe_unretained修饰符和__weak修饰符的变量一样不会持有对象,但是__unsafe_unretained修饰符的变量在销毁时并不会自动置为nil,在其地址被覆盖后就会因为反问垂悬指正而造成奔溃。因此__unsafe_unretained修饰符变量赋值给__strong修饰符变量时要确保对象的真实存在。因为__weak修饰符是在iOS5中实现的,__unsafe_unretained修饰符存在的意义就是在iOS4中代替__weak修饰符的作用。

ARCObject __unsafe_unretained *obj = nil;
{
ARCObject *obj1 = [ARCObject allocObject];
obj = obj1;
}
NSLog(@"%@(%@)", NSStringFromClass(obj.class), obj);

__outoreleasing修饰符

ARC有效时不能使用outorelease方法,也不能使用NSAutoreleasePool类。这样就导致outorelease无法直接使用,但实际上outorelease功能是起作用的。使用@outoreleasepool{}块代码来代替NSAutoreleasePool类对象的生成持有以及废弃。通过赋值给__outoreleasing修饰符的变量来代替调用outorelease方法,也就是说对象被注册到autoreleasepool中。

@autoreleasepool {
ARCObject __autoreleasing *obj1 = [ARCObject allocObject];
NSLog(@"autoreleasepool块最后一行%@", obj1);
}
NSLog(@"autoreleasepool块已经结束");

ARC有效时,cocoa中由于编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到outoreleasepool中。所以非显示的使用__outoreleasing修饰符也是可以的。

NSMutableArray __weak *array = nil;
NSLog(@"作用域块开始前%@", array);
{
NSMutableArray *arr = [NSMutableArray arrayWithObject:@(1)];
array = arr;
NSLog(@"作用域块最后一行%@", array);
}
NSLog(@"作用域块已经结束%@", array);

打印结果:

2019-03-28 11:56:52.316360+0800 ProfessionalExample[82984:16680615] 作用域块开始前(null)
2019-03-28 11:56:52.316538+0800 ProfessionalExample[82984:16680615] 作用域块最后一行(
1
)
2019-03-28 11:56:52.316627+0800 ProfessionalExample[82984:16680615] 作用域块已经结束(
1
)

id的指针和对象的指针在没有显式指定修饰符时会被附加上__outoreleasing修饰符。

- (BOOL)performOperationWithError:(ARCObject **)obj {
*obj = [ARCObject object];
return NO;
}

调用方法则为如下所示,自动转化为__autoreleasing修饰符:

[self performOperationWithError:<#(ARCObject *__autoreleasing *)#>];

id的指针和对象的指针变量必须指明所有权修饰符,并且赋值的所有权修饰符必须一致:

NSObject **pObj;//编报错,没有所有权修饰符
NSObject *obj = [[NSObject alloc] init];
NSObject *__autoreleasing*pObj = &obj;//编译报错,会更改所有权属性

纠正一个比较普遍的错误认知,for循环中并不是循环结束才释放循环内的局部变量,并不是所有产生大量对象的for循环中都需要加NSAutoreleasePool,而是产生大量autorelease对象时才需要添加。如下示例代码:

for (int index = 0; index < 2; index++) {
if (index == 0) {
NSLog(@"-------------begin");
ARCObject *obj = [[ARCObject alloc] init];
NSLog(@"%@(%@)生成了", NSStringFromClass(obj.class), obj);
}
if (index == 1) {
NSLog(@"-------------end");
}
}

下面是这段代码的打印内容:

2019-03-28 15:27:19.179194+0800 ProfessionalExample[85692:16955598] -------------begin
2019-03-28 15:27:19.179366+0800 ProfessionalExample[85692:16955598] ARCObject(<ARCObject: 0x600001ded3a0>)生成了
2019-03-28 15:27:19.179449+0800 ProfessionalExample[85692:16955598] ARCObject(<ARCObject: 0x600001ded3a0>)销毁了
2019-03-28 15:27:19.179521+0800 ProfessionalExample[85692:16955598] -------------end

ARC编码规则

1、不能使用retain/release/retainCount/autorelease
2、不能使用NSAllocateObject/NSDeallocateObject
3、须遵守内存管理的方法命名规则
4、不能显式调用dealloc方法
5、使用@autoreleasepool{}代替NSAutoreleasePool
6、不能使用NSZone
7、对象变量不能作为c语言结构体的成员
8、显式转换id和void *

内存管理的方法命名规则

以alloc/new/copy/mutableCopy开头的方法返回对象时,必须返回给调用方应当持有的对象。这在ARC有效时是一样的,不同的是以init开头的方法必须是实例方法且需要返回对象,该返回对象并不注册到autoreleasepool上。

对象变量不能作为c语言结构体的成员

要把对象类型变量加入到结构体中,需强制转为void *或者前面附加__unsafe_unretained修饰符。

显式转换id和void *

可以使用(__bridge)转换void *和OC对象,但是其安全性和赋值给__unsafe_unretained修饰符相近或者更低。如果管理时不注意赋值对象的所有者就会因为垂悬指针而奔溃或者内存泄漏。

NSObject *obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
obj = (__bridge NSObject *)p;

__bridge_retained转换可使要赋值的变量持有所赋值的变量。__bridge_transfer则与之相反。

NSObject *obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;
obj = (__bridge_transfer NSObject *)p;

NSObject对象与Core Fundation对象之间的相互转换,即免费桥(Toll-Freee-Bridge)转换。CFBridgingRetain函数(等价于__bridge_retained转换),CFBridgingRelease函数(等价于__bridge_transfer)。

NSObject *obj = [[NSObject alloc] init];
CFTypeRef ref = CFBridgingRetain(obj);
obj = CFBridgingRelease(ref);

属性

属性声明的属性与所有权修饰符对应关系

17b7bb26cbb58d6e5f931a87891b762a.png

c数组

c静态数组,各修饰符的使用OC对象一样没有区别。

以__strong为例,其初始化为nil,超过作用域销毁:

{
ARCObject *array[2];
array[0] = [ARCObject allocObject];
NSLog(@"array第一个元素:%@", array[0]);
NSLog(@"array第二个元素:%@", array[1]);
array[1] = nil;
NSLog(@"array第二个元素:%@", array[1]);
}
NSLog(@"作用域块已经结束");

打印结果:

2019-03-28 19:19:26.697408+0800 ProfessionalExample[88859:17353905] ARCObject(<ARCObject: 0x6000005f8500>)生成了
2019-03-28 19:19:26.697661+0800 ProfessionalExample[88859:17353905] array第一个元素:<ARCObject: 0x6000005f8500>
2019-03-28 19:19:26.697761+0800 ProfessionalExample[88859:17353905] array第二个元素:(null)
2019-03-28 19:19:26.697845+0800 ProfessionalExample[88859:17353905] array第二个元素:(null)
2019-03-28 19:19:26.697930+0800 ProfessionalExample[88859:17353905] ARCObject(<ARCObject: 0x6000005f8500>)销毁了
2019-03-28 19:19:26.697995+0800 ProfessionalExample[88859:17353905] 作用域块已经结束

c动态数组,c语言中动态数组声明用指针即id *array(NSObject **array)。需要注意如下几点:

1、_strong/__weak修饰符的OC变量初始化为nil,并不代表其指针初始化为nil。所以分配内存后,需要对其初始化为nil,否则非常危险。calloc函数分配的就是nil初始化后的内存,malloc函数分配内存后必须使用memset将内存填充为0(nil)。
2、必须置空_strong修饰符的态数数组内的元素,使其强引用失效,元素才能释放。因为动态数组的生命周期有开发者管理,编译器不能确定销毁动态数组内元素的时机。

{
ARCObject *__strong *array;
array = (ARCObject *__strong *)calloc(2, sizeof(ARCObject *));
NSLog(@"array第一个元素:%@", array[0]);
NSLog(@"array第二个元素:%@", array[1]);
array[0] = [ARCObject allocObject];
array[1] = [ARCObject allocObject];
array[0] = nil;
NSLog(@"array第一个元素:%@", array[0]);
NSLog(@"array第二个元素:%@", array[1]);
free(array);
}
NSLog(@"作用域块已经结束");

打印结果:

2019-03-28 19:29:26.162245+0800 ProfessionalExample[89048:17394552] array第一个元素:(null)
2019-03-28 19:29:26.162586+0800 ProfessionalExample[89048:17394552] array第二个元素:(null)
2019-03-28 19:29:26.162763+0800 ProfessionalExample[89048:17394552] ARCObject(<ARCObject: 0x600001a32b40>)生成了
2019-03-28 19:29:26.162867+0800 ProfessionalExample[89048:17394552] ARCObject(<ARCObject: 0x600001a395c0>)生成了
2019-03-28 19:29:26.162945+0800 ProfessionalExample[89048:17394552] ARCObject(<ARCObject: 0x600001a32b40>)销毁了
2019-03-28 19:29:26.163011+0800 ProfessionalExample[89048:17394552] array第一个元素:(null)
2019-03-28 19:29:26.163083+0800 ProfessionalExample[89048:17394552] array第二个元素:<ARCObject: 0x600001a395c0>
2019-03-28 19:29:26.163160+0800 ProfessionalExample[89048:17394552] 作用域块已经结束

转自:https://www.jianshu.com/p/82849c350b0b

0 个评论

要回复文章请先登录注册