arc模式下的一些属性介绍

自从引进了iOS5,iPhone,iPad,iPodTouch编程改变了很多。整个runtime和objective-c的编码方式发生了戏剧性的改变。ARC(Automatic Reference Counting - 自动引用计数)技术现在引入到LLVM编译器(LLVM Compiler)中,在某些方面,使程序更加灵活,另一方面使runtime更加脆弱。 在这章中,我们将谈论,在objective-c runtime ARC下,如何使用对象,以及怎么管理它们。


从手工引用计数到自动引用计数(ARC)
            Problem         
        学习Automatic Reference Counting(自动应用计数器),苹果的新的编译器解决了令人头痛的问题:在Objective-C中,当使用对象的时候,处理对象,以及管理内存。
         
            Solution
          由最新的LLVM编译器引进的新的属性存储技术:strong, weak,和unsafe_unretained

          Discussion
          在最新的LLVM编译器中,是使用了Automatic Reference Counting(ARC),我们需要处理strong,weak,或者unsafe和unretained存储技术。在ARC模式之下的对象,是由 :
     strong: 这种类型的对象在run-time的时候自动增加引用计数,并在它的范围结束之前有效,之外就会被自动释放【will be valid until the end of its scope, where it will automatically be released】 。熟悉Objective-C传统的内存管理办法,这个关键字和retain关键字相似。

     weak: 这是 “归零弱引用 - zeroing weak referencing”。如果变量使用这个关键字定义,当对象(变量指向的内存)被销毁了,这个变量被设置为nil。例如,你有一个strong的string属性和一个weak的string属性,并将weak属性的值设置为strongoing属性的值,当strong属性被销毁了,那么weak属性就会被设置为nil。

     unsafe_unretained:简单的将一个变量指向另一个变量。设置对象的新数值,不会增加它的引用计数,它只是简单的为变量分配了一个对象。

          默认,所有的局部变量(local variables)是strong变量。相反,属性必须明确指定它们的存储属性( storage attribute )。换句话,除了存储属性(a storage attribute,默认的是 strong properties),编译器不会承担所有属性的管理。所以,你需要确定将你的属性指定为存储属性(a storage attribute)。让我们看一看strong属性的例子。
                    #import <UIKit/UIKit.h>
                    @interface Moving_from_Manual_Reference_Counting_to_ARCAppDelegate : UIResponder <UIApplicationDelegate>
                    @property (strong, nonatomic) UIWindow *window;
                    @property (nonatomic, strong) NSString *string1;
                    @property (nonatomic, strong) NSString *string2;
                    @end

                    如果使用@"String 1"初始化string1,并将string1的属性值分配给string2,那么有着存储属性(storage attribute)的string2属性将会保存string1的值,即使在string1销毁后,string2仍然保存@"String 1"值。
                    #import "Moving_from_Manual_Reference_Counting_to_ARCAppDelegate.h"
                     @implementation Moving_from_Manual_Reference_Counting_to_ARCAppDelegate
                    @synthesize window = _window;
                    @synthesize string1;
                    @synthesize string2;
                    - (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
                                   self.string1 = @"String 1";
                                   self.string2 = self.string1; self.string1 = nil;
                                   NSLog(@"String 2 = %@", self.string2);
                                   self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
                                   self.window.backgroundColor = [UIColor whiteColor];
                                   [self.window makeKeyAndVisible];
                                   return YES;
                    }

          输出为:String 2 = String 1

          在声明属性(declaring properties)的时候strong, weak, unsafe_unretained会经常被用到。即使在声明局部变量的时候你也可以使用这些存储说明符(storage specifiers),但是你需要对说明符做一点修改。strong等效的嵌入说明符为__strong,weak等效的嵌入说明符为__weak,unsafe_unretained等效的嵌入说明符为__unsafe_unretained。(记住这些关键字是以2个下划线开头),例子如下:
                    - (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
                    /* All local variables are by default strong so just emphasis that.
                          We really don't have to mention __strong for the first variable but to make it clear,
                          we will set it. No harm in doing so. */
                    __strong NSString *yourString = @"Your String";
                    __weak NSString *myString = yourString;
                    yourString = nil;
                    __unsafe_unretained NSString *theirString = myString;

                    /* All pointers will be nil at this time */
                    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
                    self.window.backgroundColor = [UIColor whiteColor];
                    [self.window makeKeyAndVisible];
                    return YES;
                    }

           当程序员声明的object被销毁了,这时候要解决很多问题,但是,ARC中的“归零弱引用 - zeroing weak referencing”很有用,帮助程序员解决这种情况下的很多问题。weak引用:当指针指向的对象被销毁了,那么weak引用的对象(a zeroing reference's object)会被设置为nil。所有指向被回收对象的weak引用(the weak referencing pointers),都会被设置为nil。
          unsafe_unretained存储说明符是真正安全的,就像它的名字寓意。它安全的原因是:当unsafe_unretained变量指向的对象(object)被回收销毁了,这个变量会被设置为nil,并且将会指向内存中的a dangling location。访问这个地址可能引起程序的崩溃。为了避免这种情况,你应该使用“归零弱引用存储说明符 - zeroing weak referencing storage specifier”,weak或者内嵌(inline equivalent)说明符。
          我们看一下“归零弱引用存储说明符 - zeroing weak referencing storage specifier”的例子:
                    #import <UIKit/UIKit.h>
                    @interface Moving_from_Manual_Reference_Counting_to_ARCAppDelegate : UIResponder <UIApplicationDelegate>
                    @property (strong, nonatomic) UIWindow *window;
                    @property (nonatomic, strong) NSString *string1;
                    @property (nonatomic, weak) NSString *string2;
                    @end

          如果程序第一次运行,我们将会初始化strong类的string1属性,然后将string1分配给string2.然后我们将string1的值设为nil。然后我们等待,这绝对是至关重要的。如果我们在将string1设置为nil之后立即答应string2的值,可能你会得到不正确的结果,而不是nil。所以,你需要确保你的应用程序的runloop除去所有无效的对象。为了实现这个目标,我们在应用程序将要进入后台的时候,我们将打印string2的值。(这是在用户将另一个应用程序移到前台时,你的应用程序进入后台)。一旦程序在后台运行,我们知道runloop已经除去了内存中所有无效对象,并且我们得到的结果将是正确的:
                    /* 3 */
                    - (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
self.string1 = [[NSString alloc] initWithUTF8String:"String 1"];
                              self.string2 = self.string1; self.string1 = nil;

                              /* All pointers will be nil at this time */
                              self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
                              self.window.backgroundColor = [UIColor whiteColor];
                              [self.window makeKeyAndVisible];
                              return YES;
                    }
                    - (void)applicationDidEnterBackground:(UIApplication *)application{
                                                  NSLog(@"String 2 = %@", self.string2);
                    }
          现在运行程序,等到1-2秒钟后,点击Home按钮。你将会注意到接下来的结果将会被打印到控制台中:
                              String 2 = (null)

          这很容易证明ARC的“归零弱引用 - zeroing weak referencing”,实现的很完美。现在检查unsafe_unretained存储说明符是多么的危险,代码例子:
                    #import <UIKit/UIKit.h>
                    @interface Moving_from_Manual_Reference_Counting_to_ARCAppDelegate : UIResponder <UIApplicationDelegate>
                    @property (strong, nonatomic) UIWindow *window;
                    @property (nonatomic, strong) NSString *string1;
                    @property (nonatomic, unsafe_unretained) NSString *string2;
                    @end
         AppDelegate的实现部分,就像我们之前实现的一样,在程序进入后台的时候打印string2的值,并重复打开和最小化程序的操作,程序和crash!这就意味但我们的应用程序被送到后台的时候,我们试图打印内存中无效对象(string2指向的对象)的内容。既然,string2属性不安全并且 unretained(没有retain),所以string2不知道它指向的对象(in string1),当string1被设置为nil,已经被回收销毁。
          除了,上述的3个存储说明符(storage specifiers),我们还可以使用__autoreleasing说明符。当我们想要向方法传递对象的引用的时候,这个存储说明符(storage specifiers)是最方便的。例如,如果方法需要向调用方法(the caller method),传递一个NSError的error对象,方法将会向调用方法(the caller method)传递一个未初始化并且未分配的NSError实例对象。这就意味着调用者不需要分配这个error对象,所以,我们的方法就应该这样做。为了实现这个,error参数需要是自动释放对象,在合适的时间点的时候,通过runtime释放。
                    - (void) generateErrorInVariable:(__autoreleasing NSError **)paramError{ //the caller method,声明参数的类型是自动释放的类型
                              NSArray *objects = [[NSArray alloc] initWithObjects:@"A simple error", nil];
                              NSArray *keys = [[NSArray alloc] initWithObjects:NSLocalizedDescriptionKey, nil];
                              NSDictionary *errorDictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
                              *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
                    }

                    - (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
                              NSError *error = nil;
                              [self generateErrorInVariable:&error];
                              NSLog(@"Error = %@", error);
                              self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
                              self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];
                              return YES;
                    }

ivanliu2012-05-03 10:09
With ARC, I can no longer cast CGColorRef to id. I learned that I need to do a bridged cast. According clang docs:

A bridged cast is a C-style cast annotated with one of three keywords:

(__bridge T) op casts the operand to the destination type T. If T is a retainable object pointer type, then op must have a non-retainable pointer type. If T is a non-retainable pointer type, then op must have a retainable object pointer type. Otherwise the cast is ill-formed. There is no transfer of ownership, and ARC inserts no retain operations.

(__bridge_retained T) op casts the operand, which must have retainable object pointer type, to the destination type, which must be a non-retainable pointer type. ARC retains the value, subject to the usual optimizations on local values, and the recipient is responsible for balancing that +1.

(__bridge_transfer T) op casts the operand, which must have non-retainable pointer type, to the destination type, which must be a retainable object pointer type. ARC will release the value at the end of the enclosing full-expression, subject to the usual optimizations on local values.

These casts are required in order to transfer objects in and out of ARC control; see the rationale in the section on conversion of retainable object pointers.

Using a __bridge_retained or __bridge_transfer cast purely to convince ARC to emit an unbalanced retain or release, respectively, is poor form.

In what kind of situations would I use each?

For example, CAGradientLayer has a colors property which accepts an array of CGColorRefs. My guess is that I should use __brige here, but exactly why I should (or should not) is unclear.


Answer:

I agree that the description is confusing. Since I just grasped them, I'll try to summarize:

(__bridge_transfer <NSType>) op or alternatively CFBridgingRelease(op) is used to consume a retain-count of an CFObject while transfering it over to ARC. This could also be represented by id someObj = (__bridge <NSType>) op; CFRelease(op);

(__bridge_retained <CFType>) op or alternatively CFBridgingRetain(op) is used to hand an NSObject over to CF-Land while giving it a +1 retain count. You should handle an CFTypeRef you create this way the same as you would e.g. handle a result of CFStringCreateCopy(). This could also be represented by CFRetain((__bridge CFType)op); CFTypeRef someTypeRef = (__bridge CFType)op;

__bridge just casts between pointer-land and Objective-C Object Land. If you have no inclination to use the converions above, use this one.

Maybe this is helpful. I myself prefer the CFBridging… macros quite a bit over the plain casts.





__bridge只做类型转换,但是不修改对象(内存)管理权;
__bridge_retained(也可以使用CFBridgingRetain)将Objective-C的对象转换为Core Foundation的对象,同时将对象(内存)的管理权交给我们,后续需要使用CFRelease或者相关方法来释放对象;
__bridge_transfer(也可以使用CFBridgingRelease)将Core Foundation的对象转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。

PS:  ARC is supported in Xcode 4.2 for Mac OS X v10.6 and v10.7 (64-bit applications) and for iOS 4 and iOS 5. Weak references are not supported in Mac OS X v10.6 and iOS 4. There is no ARC support in Xcode 4.1 and earlier.

参考文章:
http://www.yifeiyang.net/development-of-the-iphone-simply-6/
https://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html





Managing Toll-Free Bridging
In many Cocoa applications, you need to use Core Foundation-style objects, whether from the Core Foundation framework itself (such as CFArrayRef or CFMutableDictionaryRef) or from frameworks that adopt Core Foundation conventions such as Core Graphics (you might use types like CGColorSpaceRef and CGGradientRef).
The compiler does not automatically manage the lifetimes of Core Foundation objects; you must call CFRetain and CFRelease (or the corresponding type-specific variants) as dictated by the Core Foundation memory management rules (see Memory Management Programming Guide for Core Foundation).
If you cast between Objective-C and Core Foundation-style objects, you need to tell the compiler about the ownership semantics of the object using either a cast (defined in objc/runtime.h) or a Core Foundation-style macro (defined in NSObject.h):
__bridge transfers a pointer between Objective-C and Core Foundation with no transfer of ownership.
__bridge_retained or CFBridgingRetain casts an Objective-C pointer to a Core Foundation pointer and also transfers ownership to you.
You are responsible for calling CFRelease or a related function to relinquish ownership of the object.
__bridge_transfer or CFBridgingRelease moves a non-Objective-C pointer to Objective-C and also transfers ownership to ARC.
ARC is responsible for relinquishing ownership of the object.
For example, if you had code like this:
- (void)logFirstNameOfPerson:(ABRecordRef)person {

    NSString *name = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
    NSLog(@"Person's first name: %@", name);
    [name release];
}
you could replace it with:
- (void)logFirstNameOfPerson:(ABRecordRef)person {

    NSString *name = (NSString *)CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));
    NSLog(@"Person's first name: %@", name);
}
The Compiler Handles CF Objects Returned From Cocoa Methods
The compiler understands Objective-C methods that return Core Foundation types follow the historical Cocoa naming conventions (see Advanced Memory Management Programming Guide). For example, the compiler knows that, in iOS, the CGColor returned by the CGColor method of UIColor is not owned. You must still use an appropriate type cast, as illustrated by this example:
NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
[colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
Cast Function Parameters Using Ownership Keywords
When you cast between Objective-C and Core Foundation objects in function calls, you need to tell the compiler about the ownership semantics of the passed object. The ownership rules for Core Foundation objects are those specified in the Core Foundation memory management rules (see Memory Management Programming Guide for Core Foundation); rules for Objective-C objects are specified in Advanced Memory Management Programming Guide.
In the following code fragment, the array passed to the CGGradientCreateWithColors function requires an appropriate cast. Ownership of the object returned by arrayWithObjects: is not passed to the function, thus the cast is __bridge.
NSArray *colors = <#An array of colors#>;
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
The code fragment is shown in context in the following method implementation. Notice also the use of Core Foundation memory management functions where dictated by the Core Foundation memory management rules.
- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
    CGFloat locations[2] = {0.0, 1.0};
    NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
    [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
    CGColorSpaceRelease(colorSpace);  // Release owned Core Foundation object.
    CGPoint startPoint = CGPointMake(0.0, 0.0);
    CGPoint endPoint = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
    CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint,
                                kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
    CGGradientRelease(gradient);  // Release owned Core Foundation object.
}


ivanliu2012-05-03 11:23
在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检测,没有内存泄漏。




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

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

#pragma mark – View lifecycle 
- (void)viewDidLoad 

    [super viewDidLoad]; 
    NSLog(@"=%@", [self escape:@"wangjun"]); 

-(NSString *)escape:(NSString *)text 

    return (__bridge_transfer NSString *)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 (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes( 
                                                                      NULL, 
                                                                      (__bridge CFStringRef)text, 
                                                                      NULL, 
                                                                      CFSTR("!*’();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));; 
}
编译也会成功,但是这时候用instruments检测,可以发现内存泄漏:



由于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.

ivanliu2012-05-03 11:39
在cocoachina上看到一贴友使用将asihttp打成framework的方式引用,我按照他思路弄完之后,仍有错误。不过他的思路还是先要赞扬的。
后来,我自己研究了下ios5sdk,发现了另一种解决办法。
targets->build phrase->compile sources有很多.m文件,在文件后面有一个compiler flags选项。针对asihttp框架下的.m文件,点击对应的compiler flags,输入-fno-objc-arc,然后编译 运行,OK,成功。

看来文件针对arc编译,具有可选择性
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值