解决__bridge NSString *urlstr 内存泄露

Managing Toll-Free Bridging


在cocoa application的应用中,我们有时会使用Core Foundation(CF),我们经常会在Objective-C和CF之间进行转化。系统使用arc的状态下,编译器不能自动管理CF的内存,这时候你必须使用CFRetain和CFRelease来进行CF的内存的管理。

具体的CF内存管理规则见: Memory Management Programming Guide for Core Foundation

在OC和FC之间进行转化的时候,主要是对象的归属问题。共有两种方式:

1、使用宏,可以标识归属者从OC到CF,还是从CF到OC。

NS_INLINE CFTypeRef CFBridgingRetain(id X) {     
    return (__bridge_retain CFTypeRef)X;      
}      
  
NS_INLINE id CFBridgingRelease(CFTypeRef X) {      
    return (__bridge_transfer id)X;      
}

2、使用转化符,如:__bridge,__bridge_transfer,__bridge_retained

id my_id;     
CFStringRef my_cfref;      
…      
NSString   *a = (__bridge NSString*)my_cfref;     // Noop cast.      
CFStringRef b = (__bridge CFStringRef)my_id;      // Noop cast.      
…      
NSString   *c = (__bridge_transfer NSString*)my_cfref; // -1 on the CFRef      
CFStringRef d = (__bridge_retained CFStringRef)my_id;  // returned CFRef is +1

下面以详细的例子来介绍一下OC和CF在arc下内存管理的详细写法.下面以CFURLCreateStringByAddingPercentEscapes()函数为例说一下在ARC下的写法和非ARC下的写法。

非ARC模式下的写法:

#pragma mark – View lifecycle     
- (void)viewDidLoad      
{      
    [super viewDidLoad];      
    NSLog(@"=%@", [self escape:@"wangjun"]);      
}      
-(NSString *)escape:(NSString *)text      
{      
    return (NSString *)CFURLCreateStringByAddingPercentEscapes(      
                                                                      NULL,      
                                                                      (__bridge CFStringRef)text,      
                                                                      NULL,      
                                                                      CFSTR("!*’();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));;      
}

使用instruments检测,没有内存泄漏。

image

下面把上面工程改为arc模式。

可以看到xcode自动把上面函数转化为:

#pragma mark – View lifecycle     
- (void)viewDidLoad      
{      
    [super viewDidLoad];      
    NSLog(@"=%@", [self escape:@"wangjun"]);      
}      
-(NSString *)escape:(NSString *)text      
{      
    return (__bridge_transferNSString *)CFURLCreateStringByAddingPercentEscapes(      
                                                                      NULL,      
                                                                      (__bridge CFStringRef)text,      
                                                                      NULL,      
                                                                      CFSTR("!*’();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));;      
}

在arc中,CF和OC之间的转化桥梁是 __bridge,有两种方式:

__bridge_transfer  ARC接管管理内存

__bridge_retained  ARC释放内存管理

上面的方法是从CF转化为OC NSString对象,使用的__bridge_transfer ,对象所有者发生转变,由CF到OC,最后由ARC接管内存管理。运行上面的代码,用instruments检测,是没有内存泄漏的。

上面代码等同于:

- (NSString *)escape:(NSString *)text     
{      
return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(      
NULL,      
(__bridge CFStringRef)text,      
NULL,      
CFSTR("!*’();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)));}

如果将上述代码改为:

-(NSString *)escape:(NSString *)text     
{      
    return (__bridgeNSString *)CFURLCreateStringByAddingPercentEscapes(      
                                                                      NULL,      
                                                                      (__bridge CFStringRef)text,      
                                                                      NULL,      
                                                                      CFSTR("!*’();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));;      
}      

编译也会成功,但是这时候用instruments检测,可以发现内存泄漏:

image

由于CF转化完OC,没有自己释放内存,同时也没有把内存管理交给ARC,所以出现内存泄漏。由于__bridge只是同一个对象的引用,内存的所有权没有发生变化。

下面在说一下oc到CF的转化,需要把OC的内存管理权释放掉。

NSString *s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name];     
CFStringRef s2 = (__bridge_retained CFStringRef)s1;      
// do something with s2 // . . .      
CFRelease(s2);

最后由CF进行内存释放。

上面代码等同于:

CFStringRef s2 = CFBridgingRetain(s1);     
// . . .      
CFRelease(s2);

下面总结一下我们使用ARC情况下。oc和CF互相转化的原则:

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

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

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




ARC中管理Toll-free Bridging

ARC对象和非ARC对象

对于初学者,首先需要分清楚两种不同种类的对象:
  1. Objective-C 对象,它继承自NSObject的所有对象。在ARC中,我们可以理解为ARC对象。
  2. Core Foundation 对象,它是由C的struct定义的各种对象,主要来自于CoreFoundation框架(如CFArray或者CFMutableDictionaryRef类型),或者其它采用CoreFoundation命名规范的框架,如Core Graphics(如,CGColorSpaceRef 和CGGradientRef)。我们可以理解为Non-ARC 对象。
ARC 编译器目前不会自动管理CoreFoundation 对象的生命周期。因此程序员需要调用CFRetain 和CFRelease来手动管理这些对象。  在 非ARC编译器时代,Objective C对象和Core Foundation对象之间可以无缝衔接(Toll-FreeBridging),他们可以采用类型转化(cast)自由互相转化。但在ARC编译器中,ObjectiveC对象的生命周期由编译器负责,因此在这种无缝转化中,程序员需要使用新的类型转化关键字或者类似CF的宏,让编译器知道对象的所有者是编译器还是程序员。这些新的关键字是:

·        __bridge (定义在objc/runtime.h)在ARC对象和Non-ARC对象之间,简单得进行指针值得传递,没有所有权的转移。

·        __bridge_transfer(CFBridgingRelease (定义在NSObject.h))用于转换Non-ARC指针到ARC指针,并且将对象所有权转移给ARC,这样,ARC将释放该对象。

·        __bridge_retained(CFBridgingRetain)用于转化ARC指针到Non-ARC指针,并转移所有权给程序员。 程序员需要以后调用CFRelease或以其他方式释放对象的所有权。


如何使用这些特殊注释关键字

ARC只能管理Objective-C的类型。CoreFoundation类型仍然必须由程序员来手动管理。有关所有权,因为有歧义,ARC禁止Objective-C对象的指针和其他类型,包括的CoreFoundation对象的指针的指针之间的标准转化。在Non-ARC编译中,下面的代码是非常典型的手动内存管理下,

    OBJ  =  (ID )CFDictionaryGetValue的(cfDict , key);

在ARC编译中,为了使其能重新编译,你必须使用上面的特殊注释关键字(__bridge,__bridge_retained,__bridge_transfer),向编译器说明对象的所有权。。

__bridge

最简单的理解是__bridge。这是一个没有所有权后果的直接转换。ARC接收值,然后正常管理。这就是我们想为上述:

    ID OBJ =  (__bridge  ID )CFDictionaryGetValue, (cfDict , key);

其他两个强转注释涉及所有权的转让。他们可以帮助缓解来回ARC- non-ARC类型转化的痛苦。让我们来看看。

下面是一个在NonARC,返回的对象需要被释放的情况下使用桥接的例子。

   NSString *value = (NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"),CFSTR("com.company.someapp"));

    [self useValue: value];

   [value release];

如果在ARC中, 我们使用 __bridge,删除release,其它代码不作任何修改,下面的代码会产生内存泄漏:

   NSString *value = (__bridge NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"),CFSTR("com.company.someapp"));

   [self useValue: value];

在这段代码Copy的对象需要被释放。ARC将retain初始化时value,然后当value不再使用的时候释放value。因为没有平衡原始的copy 对象,该对象被泄漏了。

我们可以用下面的代码解决这一问题:

   CFStringRef valueCF = CFPreferencesCopyAppValue(CFSTR("someKey"),CFSTR("com.company.someapp"));

   NSString *value = (__bridge NSString *)valueCF;

    CFRelease(valueCF);

 

   [self useValue:value];

这是相当冗长,虽然。由于无缝衔接的关键就是要尽可能让程序员不要太痛苦的处理这种代码,整个ARC就是消除需要编写内存管理代码。如果可以变得更简单,那就再好不过了。

__bridge_transfer

__bridge_transfer注解解决这个问题。而不是简单地移动到ARC的指针值,它转移了指针的值和对象的所有权。当__bridge_transfer在强转中使用时,它告诉ARC这个对象已经保留,ARC并不需要再保留它。由于ARC拥有所有权,当这个对象不再需要的时候,它仍然会释放它。最终的比较清爽的代码将是:

    NSString *value = (__bridge_transfer NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp"));

    [self useValue: value];

__bridge_retained

Toll-free bridging是双向的。如前所述,ARC不允许一个Objective-C对象的指针转换到的CoreFoundation对象的指针。在ARC, 此代码不会编译成功:

    CFStringRef value  (CFStringRef )[ self someString ];

    UseCFStringValue (value);

使用__bridge转化才可以编译,但产生的代码是不稳定的:

    CFStringRef value  (__bridge  CFStringRef )[self.someString];

    UseCFStringValue (value);

由于ARC不再管理value的生命周期,它会立即释放value对象的所有权。 在value被传递到UseCFStringValue之前,value可能已经被释放,因此可能会导致崩溃或其他不当行为。通过使用__bridge_retained,我们可以告诉ARC系统将对象所有权转移给程序员。由于所有权发生转移的,程序员现在需要负责释放对象,就像任何其他CF代码:

    CFStringRef value = (__bridge_retained CFStringRef)[selfsomeString];

    UseCFStringValue(value);

    CFRelease(value);

在Toll-free bridging之外,这些强转的注释也是有用的。任何时候,你需要存储一个Objective-C对象指针到非ARC对象的时候,他们为你铺平道路。 在Cocoa中,经常会看到void *指针,一个突出的例子是sheets。在非ARC中:

    NSDictionary *contextDict = [NSDictionary dictionary...];

    [NSApp beginSheet: sheetWindow

       modalForWindow: mainWindow

        modalDelegate: self

       didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)

          contextInfo:[contextDictretain]];

 

 

    - (void)sheetDidEnd: (NSWindow *)sheet returnCode: (NSInteger)code contextInfo: (void*)contextInfo

    {

        NSDictionary *contextDict = [(id)contextInfoautorelease];

        if(code == NSRunStoppedResponse)

            ...

    }

同样,在ARC中,上述代码会编译失败,因为对象和非对象指针之间的转化是不允许的。然而,使用强转修饰符,我们不光能在ARC中编译通过,而且得到ARC为我们做的必要的内存管理:

    NSDictionary *contextDict = [NSDictionary dictionary...];

    [NSApp beginSheet: sheetWindow

       modalForWindow: mainWindow

        modalDelegate: self

       didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)

          contextInfo: (__bridge_retained void*)contextDict];

 

 

    - (void)sheetDidEnd: (NSWindow *)sheet returnCode: (NSInteger)code contextInfo: (void*)contextInfo

    {

        NSDictionary *contextDict = (__bridge_transfer NSDictionary *)contextInfo;

        if(code == NSRunStoppedResponse)

            ...

    }

总结:

  • __bridge只是简单的传递指针之间的值,没有所有权的转移。
  • __bridge_transfer用在非Objective-C的指针强转到Objective-C的过程中,不仅转移值,而且转移对象的所有权。这样,ARC将释放对象的值。
  • __bridge_retained用在一个Objective-C指针强转到非Objective-C指针的过程中。 不光转移指针值,而且转移对象的所有权给程序员。程序员需要调用的CFRelease或以其他方式释放对象的所有权。

在没有新产生对象的情况下,通常只是使用__bridge做指针值的传递。一旦在转换过程中产生新的对象,就需要考虑使用__bridge_tranfer 或者__bridge_retained了。将手动内存管理的系统迁移到ARC,大部分遇到的问题就是这种类型转化的问题,不过都很好解决。XCode4.4 给出了非常精准的解决方法建议。

在此顺便将我的项目转化过程做一个简要记录,希望对有问题的同学有所帮助:

1。将现有程序中的所有编译警告消除,因为迁移到新的LLVM4 编译器,自然有很多警告。

2。编译,并运行,确认程序可以正常运行。

3。决定程序的那些部分需要转为ARC,那些源程序不需要转。一般第三方,或引用他人的opensource的程序,尽可能保持其原状,不做转化。

4。使用xcode4.4提供的ARC工具:Edit->Refactor->Convert to ARC project...

5。在弹出对话框中,提示要被转化的项目名称和图标,注意在项目名称的左侧有一个小的下拉按钮,用户可以单击,进而显示所有要被转化的源文件列表,在此,用户可以选择那些文件要被转化,那些不需要,只需要取消勾选即可。

6.  单击继续按钮,系统会开始检查,并预编译程序看是否可以转为ARC,如果有编译错误,系统将提示你修复这些编译错误.大部分编译错误都是上述的类型转化的问题。

7. 解决了这些编译错误,在此尝试启动该ARC工具。


posted on 2014-02-12 17:58  金玉游龙  阅读(442)  评论(0编辑  收藏  举报

导航