OC内存管理小计

不管在ARC还是MRC中,都遵循下面两个基本原则

You own any object you create
You create an object using a method whose name begins with “alloc”, “new”, “copy”, or “mutableCopy”.

MRC

MRC的基本原则

alloc、new

OC中,在alloc、new、retain、copy、mutableCopy一个对象后

或者CF中,在Create(包含Create的函数)、CFRetain、Copy(包含Copy的函数)一个对象后

这个对象的引用记数会加1,由于这个引用记数是在当你加的,所以你有责任在不需要它的时后对它进行release操作。

这与总的基本原则也是相符的,你负责你自己用alloc, init, new, copy & mutableCopy创建的对象的生命周期。

非 alloc、new

如果不是用alloc, init, new, copy & mutableCopy创建的,这个对象就不需要由你来负责,MRC下一种实现方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* MRC,此段代码摘抄自[《Objective-C 高级编程》](https://book.douban.com/subject/24720270/)
*/
- (id)object
{
id obj = [[NSObject alloc] init];
/*
* 自己持有对象
*/
[obj autorelease];
/*
* 取得对象的存在,但自己不持有对象
*/
return obj;
}

如上,这个对象由autorelease pool来负责释放,不需要创建他的人负责释放。

MRC的AutoreleasePool

AutoreleasePool就是一个池子,给OC对象发送autorelease消息(CF对象对应的是CFAutorelease函数)后就把对象放到池子中,然后在池子结束的地方,对池子中的每一个对象都发送一次release消息。

ARC

ARC简单的理解就是编译器自动在代码合适的位置加入retain和release代码,背后没那么简单,这里也不深究了,ARC只支持OC对象。

ARC中,不能对OC对象进行retain、release、autorelease操作。

在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop

黑幕背后的Autorelease

在ARC中,虽然不可以使用retain、release、autorelease,但是可以使用

1
2
3
@autoreleasepool{
...
}

这样可以在进行大的内存操作时,手动的进行一些内存管理。

alloc、new 与非 alloc、new 构建的对象

在 ARC 中,使用 alloc、new 开头的方法名生成的对象不会加入到 Autorelease 池中

在 ARC 中,使用非 alloc、new 开头的方法名生成的对象会自动加入到 Autorelease 池中

这就意味着使用 alloc、new 开头的方法名生成的对象,一旦没有引用时,就会被释放。

而非 alloc、new 开头的方法名生成的对象,即使没有引用时,也不会马上被释放,而是在Autorelease 池中被释放。

如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
+ (NSString *)newHelloWorldString {
return [[NSString alloc] initWithCString:"HelloWorld" encoding:NSUTF8StringEncoding];
}
+ (NSString *)helloWorldString {
return [[NSString alloc] initWithCString:"HelloWorld" encoding:NSUTF8StringEncoding];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
__weak NSString *helloWorldString = [XSQObject helloWorldString];
__weak NSString *newHelloWorldString = [XSQObject newHelloWorldString];
//此处有warning:
//assigning retained object to weak variable;
//object will be released after assignment
NSLog(@"%@", helloWorldString);//输出HelloWorld
NSLog(@"%@", newHelloWorldString);//输出null
}
return 0;
}

ARC 下的指针修饰__weak__strong__autoreleasing

不管是 ARC 还是 MRC,判断对象是否释放的原则都是看对象是否被强引用。

默认的指针是 strong 类型,强引用一个对象,如果这个对象没有被强引用是,立即释放。

__weak 修饰的指针是弱引用指针,如果 alloc 一个对象,用__weak指针指向,这个对象一生成就立马被释放了。

__autoreleasing修饰的指针可以理解为是弱引用这个对象的,但是把这个对象加入自动释放池,这样即使不引用这个对象时,也不会被立即释放。

具体可以看下面的代码说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//没有引用的对象,一初始化就立即被释放
[[NSObject alloc] init];
//weak引用刚初始化的对象,一初始化就立即被释放
__weak NSObject *w = [[NSObject alloc] init];
//默认的指针是__strong类型的,持有的对象不会被释放,当对象没有被持有时,则立即被释放
NSObject *a = [[NSObject alloc] init];
a = nil;
//a 之前指向的对象,在程序执行到这里时已经被释放
//__strong类型的,持有的对象不会被释放,当对象没有被持有时,则立即被释放
__strong NSObject *c = [[NSObject alloc] init];
c = nil;
//c 之前指向的对象,在程序执行到这里时已经被释放
//__autoreleasing类型的,会将对象加入自动释放池
__autoreleasing NSObject *b = [[NSObject alloc] init];
b = nil;
//b 之前指向的对象,在程序执行到这里时还未被释放,现在该对象被自动释放池持有

函数返回问题

这里只记录返回CF对象的情况,因为OC对象有ARC,应该也很少会遇到这个问题了,我也没去研究。

如下的函数

1
2
3
4
5
6
7
8
9
10
11
//OC
+ (CGColorSpaceRef)getAColorSpace {
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
return colorSpace;
}
//CF
CGColorSpaceRef getAColorSpace(){
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
return colorSpace;
}

如果我们运行Analyze,编辑器会给我们一个Potential leak of an object stored into ‘colorSpace’的提示。

原因就是我们调用了CF的Create函数生成了一个CF对象,当前代码片段不需要时又没有release它。

那可怎么办,总不能Create完又release,然后返回个空吧?

有三种办法避免这个

  • 第一种是修改函数名,可以改成如下的名字

    1
    2
    3
    4
    //OC
    + (CGColorSpaceRef)allocAColorSpace {...}
    //CF
    CGColorSpaceRef CreateAColorSpace(){...}

    这样,名字中包含alloc、Create,说明调用这个方法的代码持有这个方法返回的对象,对象的释放应该由外面的代码来管理。

    OC中包含new、alloc、copy的方法名

    CF中包含Create、Copy的方法名

    都代表返回的对象调用这个方法的代码来管理,如最开始所说的,我们再不需要这个实例时在调用CFRelease来释放。

  • 第二种是给colorSpace添加autorelease,像下面这样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    + (CGColorSpaceRef)getAColorSpace {
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    CFAutorelease(colorSpace);
    return colorSpace;
    }
    CGColorSpaceRef getAColorSpace(){
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    CFAutorelease(colorSpace);
    return colorSpace;
    }

    这样,就把colorSpace放入autorelease pool,把这个对象管理的权利交给autorelease pool,colorSpace不会马上释放,外面的代码也能用到它。

  • 第三种是将CF对象Toll-Free转换为OC对象,由ARC管理内存

方法命名与释放

另外,ARC中,创建实例的方法的命名会影响创建的对象的释放

新建实例的方法由alloc, init, new, copy & mutableCopy开头命名

对象有两种释放方式:

  • 没用引用了就直接释放
  • 调用Autorelease,由Autorelease pool释放

不同的方法前缀会有不同的效果,

详情可参见 ARC and releasing object created in method

Toll-Free

苹果官方文档

总结:

  • CF转化为OC时,并且对象的所有者发生改变,则使用CFBridgingRelease()__bridge_transfer
  • OC转化为CF时,并且对象的所有者发生改变,则使用CFBridgingRetain()__bridge_retained

当一个类型转化到另一种类型时,但是对象所有者没有发生改变,则使用__bridge.

http://www.beyondabel.com/blog/2014/03/05/mrc-arc/

1
2
3
4
CFArrayRef array;
...
NSArray *nArray = (__bridge_transfer NSArray *) array; //对象的所有者发生改变NSArray在ARC下会自动释放
//CFRelease(array); //这里不需要CFRelease了

与MRC混用时的疑惑

2017年07月26日记:

今天MRC与ARC混用,有些疑惑,MRC方法中初始化的实例,作为返回值传到ARC中,在ARC中需要怎么处理。

然后做了个实验

在MRC中新建TestMRCARC类,该类重载dealloc方法并输出log

然后在MRC随便某个类中添加如下方法

1
2
3
4
5
+ (TestMRCARC *)makeTestMRCARC {
TestMRCARC *s = [[TestMRCARC alloc] init];
// [s autorelease];
return s;
}

在ARC中调用该方法:

1
2
3
- (IBAction)btnClick:(id)sender {
TestMRCARC *s1 = [XXX makeTestMRCARC];
}

发现s1并没有被释放。

原因是MRC中的方法命名采用的是make开头,所以ARC中不会对s1进行管理。

解决方法:

  1. 将方法名改成newTestMRCARC,s1就会得到正确的释放
  2. 或将[s autorelease]取消注释,由ARC中的Autorelease pool来释放

另外,在ARC中,即使方法没用按照规则命名,不使用alloc、new、retain、copy、mutableCopy,变量也会得到正确的释放。但是我们再编码时,最好还是按照基本规则来命名。

参考

ARC下的autorelease