OC内存管理小计

2017-2-21

不管在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下一种实现方式如下:

/*
 * 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,但是可以使用

@autoreleasepool{
  ...
}

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

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

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

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

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

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

如下代码:

+ (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修饰的指针可以理解为是弱引用这个对象的,但是把这个对象加入自动释放池,这样即使不引用这个对象时,也不会被立即释放。

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

    //没有引用的对象,一初始化就立即被释放
    [[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,应该也很少会遇到这个问题了,我也没去研究。

如下的函数

//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,然后返回个空吧?

有三种办法避免这个

方法命名与释放

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

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

对象有两种释放方式:

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

详情可参见 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/

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随便某个类中添加如下方法

+ (TestMRCARC *)makeTestMRCARC {
    TestMRCARC *s = [[TestMRCARC alloc] init];
//    [s autorelease];
    return s;
}

在ARC中调用该方法:

- (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