difference between ARC and non ARC

ARC 需要知道它的作用域,这样才能正确的释放这个对象.

“A name is referenced outside the NSAutoreleasePool scope that it was declared in”

你可能会这样创建你自己的自动释放池:

 
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

// . . . do calculations . . .

NSArray* sortedResults =

  [[filteredResults sortedArrayUsingSelector:@selector(compare:)]

    retain];

[pool release];

return [sortedResults autorelease];

转换工具需要将它转换成这样的形式:

 
@autoreleasepool

{

  // . . . do calculations . . .      

  NSArray* sortedResults = [filteredResults sortedArrayUsingSelector:@

    selector(compare:)];    

}

return sortedResults;


“ARC 禁止在 Objective-C 中使用结构体和联合体”

ARC 的一个限制就是你不能在 C 接口体中放入 Objective-C 对象了。 下面的代码无效了:

 
typedefstruct{
    UIImage *selectedImage;
    UIImage *disabledImage;
} 
ButtonImages;

推荐的方式是将这个结构体直接转换成 Objective-C 对象。

Dealloc

让我们看看这些错误,然后一个一个的修复他们。 我们从 dealloc 开始:

descr

dealloc 中的每一行都会有一个错误。 我们不再被允许调用 [release] 了, 也不允许调用 [super dealloc]。 因为我们在 dealloc 不再做任何操作了, 所以我们可以直接把这个方法删除掉。

唯一一个留着 dealloc 方法的原因就是, 你需要释放一些不在 ARC 控制下的资源。 例如 Core Foundation 对象中调用 CFRelease(), 对那些通过 malloc() 分配的内存调用 free(), 注销通知,停止 Tiner, 等等。

因为你是用延迟加载的 SoundEffect 对象, 在你调用 soundEffect 方法之前, soundEffect 实例变量都是空的。 因此, 我们实际上有一个 SoundEffect 对象, 你应该通过 self 来访问它。 如果你发现这个模式需要你通过 @property 声明 soundEffect, 那么就去做吧。 不同的方式适合不同的人. 🙂

最为最佳实践, 如果你将一些东西定义为属性, 那么你应该一直把他们当做属性来用。 唯一一个你需要直接访问属性后面的实例对象的地方是在 init 方法中,还有你需要提供自定义的 getter 和 setter 方法的地方。 在其他地方你应该通过 self.propertyName 的方式来访问属性。 这也是为什么 synthesize 语句经常对 ivar 进行重命名:

@synthesize propertyName = _propertyName;

这种结构能够防止你在应该用 “self.propertyName” 的地方,意外的通过 “propertyName” 来直接使用后端的实例变量。

说到属性, MainViewController 的 .h 文件中, 也有两个 outlet 属性:

@property (nonatomic, retain) IBOutlet UITableView *tableView;

@property (nonatomic, retain) IBOutlet UISearchBar *searchBar;


retain 关键词在 ARC 中仍然有效, 他们只不过是 strong 的一个代号。 不过,最好还是把属性声明为 strong, 因为这才是正确的术语。 但对这两个特殊的属性来说,我有其他的计划。 我们将他们声明成 weak 的, 来代替 strong:

@property (nonatomic, weak) IBOutlet UITableView *tableView;

@property (nonatomic, weak) IBOutlet UISearchBar *searchBar;

Weak 是所有 outlet 属性推荐的声明关系。 这个视图已经是视图控制器视图层级中的一部分了, 不需要在其他地方 retain 了。 声明 weak 的好处是, 省去了你自己写 viewDidUnload 方法的时间。

概括地说, 属性的修饰符是这样:

  • strong. 这是以前的 “retain” 的一个代号。 strong 属性将会成为它所指对象的所有者。
  • weak. 这个属性表示弱引用指针。 当它指向的对象被销毁时,它会被自动的设置成 nil。 记住,在 outlet 上使用它
  • unsafe_unretained. 这和以前的 “assign” 等同。 你只需要在一些例外的情况下会应道它,并且当你在给 iOS 应用时,后面会讲到更多。
  • copy. 这个和之前是一样的。 它创建一个对象的拷贝,并且创建一个强引用关系。
  • assign. 你不再允许在对象上用这个修饰符, 但你仍然能在简单类型的值,比如 BOOL,int 和 float 中用到它。

在 ARC 之前,你是可以这样写的:

@property (nonatomic, readonly) NSString *result;

这样会间接的创建一个 assign 属性。 对于 readonly 值没有问题。 但是,如果你对 readonly 的数据使用 retained 意味着什么呢? 在 ARC 中,上面的代码会给出如下错误:

"ARC forbids synthesizing a property of an Objective-C object with unspecified

ownership or storage attribute"

Toll-Free Bridging

让我们修改最后一个方法,这样我们可以重新运行应用。

descr

这个方法里,用到了 CFURLCreateStringByAddingPercentEscapes() 函数,来对一个字符串进行 URL 编码。 我们用它来确保用户输入的搜索文本中任何的空格和其他字符都能转换成 HTTP GET 请求中有效的内容。

编译器给出了如下错误:

  • Cast of C pointer type ‘CFStringRef’ to Objective-C pointer type ‘NSString *’ requires a bridged cast
  • Cast of Objective-C pointer type ‘NSString *’ to C pointer type ‘CFStringRef’ requires a bridged cast
  • Semantic Issue: ‘autorelease’ is unavailable: not available in automatic reference counting mode
  • Automatic Reference Counting Issue: ARC forbids explicit message send of‘autorelease’

最后两个错误是一样的,并且很简单, 我们不能调用 [autorelease]。 先不管他们。

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

{

  return (NSString *)CFURLCreateStringByAddingPercentEscapes(

    NULL,

    (CFStringRef)text,

    NULL,

    (CFStringRef)@"!*'();:@&=+$,/?%#[]",

    CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));

}

其他两个错误和 “bridged” 转换有关系。 这个方法中有3个类型转换:

  • (NSString *)CFURLCreateStringByAddingPercentEscapes(…)
  • (CFStringRef)text
  • (CFStringRef)@”!*’();:@&=+$,/?%#[]“

编译器只对前两个给出了错误。 第三个是对常量对象的转换,所以不需要特别的内存管理。 这个字符串常量会打包到应用的可执行程序中。 和 “真实” 的对象不同, 它不需要分配和释放。

如果你想要处理,你可以这样写:

CFSTR("!*'();:@&=+$,/?%#[]")

CFSTR() 从一个指定的字符串创建一个 CFStringRef 对象。 这个字符串是一个标准的 C 字符串,没有以 @ 符号开头。 我们没有创建 NSString 对象,并把它转换成 CFStringRef, 而至直接创建一个 CFStringRef 对象。 你喜欢哪一个取决于你的感觉, 他们的结果是一样的。

当你在Core Foundation 和 Cocoa 中转换对象时 Bridged casts 是必须的。

现在大多数的应用都不太需要用到 Core Foundation, 你几乎可以舒服的用 Objective-C 中的类来完成你要的任何工作。 然而,一些基于 Core Foundation 的底层 API, 例如 Core Graphics 和 Core Text 他们没有 Objective-C 版本。 幸运的是, iOS 的设计者让这两个领域中对象的转换变得非常简单。 并且你不需要负责任何操作。

不管是基于任何目的, NSString 和 CFStringRef 都可以看做是一样的。 你可以将 NSString 对象当做 CFStringRef 类型来使用, 并且还可以将 CFStringRef 对象当做 NSString 类型来使用。 这是 toll-free bridging 的思想。 在以前,可以简单的这样写:

CFStringRef s1 = [[NSString alloc] initWithFormat:@"Hello, %@!",

  name];

当然,你还要记得,当你用完它的时候把它释放掉:

CFRelease(s1);

另外一种方法, 从 Core Foundation 到 Objective-C:

CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault,

  bytes, kCFStringEncodingMacRoman);

NSString *s3 = (NSString *)s2;

// release the object when you're done

[s3 release];

现在我们有了 ARC, 编译器需要知道谁来负责释放这个转换过的对象。 如果你把一个 NSObject 当做一个 Core Foundation 对象, 那么 ARC 就不再负责释放它。 但你需要告诉 ARC 你的意图, 编译器自己不能推断出来。 同样的, 如果你创建了一个 Core Foundation 对象, 但随后把它转换成了 NSObject, 你需要告诉 ARC 获取它的所有关系, 并在合适的时候释放这个对象。 这正是 bridging casts 要解决的。

让我们首先来看看简单转换。 CFURLCreateStringByAddingPercentEscapes() 函数接收一大串参数, 其中两个是 CFStringRef 对象。 他们在 Core Foundation 中等同于 NSString。 在以前,我们只需要将 NSString 对象转换成 CFStringRef 类型, 但是在 ARC 中, 编译器需要更多信息。 我们已经处理过了常量字符串, 它不需要 bridging cast, 它是一个不需要释放的特殊对象。 但是, text 参数又是另外一回事。

text 变量是一个 NSString 对象,作为一个参数传递给这个方法。 和局部变量一样, 方法参数也是强引用; 这些对象在进入方法时就被 retain 了。 text 所指向的值一直会存在, 直到这个指针被销毁。 因为它是一个局部变量, 在 escape 方法结束是,他就会被销毁。

我们希望 ARC 作为这个对象的所有者, 但我们也希望暂时的将它当做 CFStringRef。 在这种情况下, 会使用到 __bridge 修饰符。 它告诉 ARC 不需要改变所属关系, 对象还是通过默认的规则释放掉。

我们在之前的 SoundEffect.m 中已经用到过 __bridge 了:

OSStatus error = AudioServicesCreateSystemSoundID((__bridge

  CFURLRef)fileURL, &theSoundID);

这里的情况完全相同。 fileURL 变量指向了一个 被 ARC 管理的 NSURL 对象。 而 AudioServicesCreateSystemSoundID() 函数, 期望的是一个 CFURLRef 对象。 幸好 NSURL 和 CFURLRef 是 toll-free bridged 的, 所以我们可以将其中一个转换为另外一个。 因为我们仍然希望 ARC 在我们操作完成之后释放这个对象, 我们使用 __bridge 关键字来指示 ARC 仍然负责释放。

将 escape: 方法修改成这样:

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

{

  return (NSString *)CFURLCreateStringByAddingPercentEscapes(

    NULL,

    (__bridge CFStringRef)text,

    NULL,

    CFSTR("!*'();:@&=+$,/?%#[]"),

    CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));

}

我们还剩最后一个错误:

我们还剩最后一个错误:

大多数时候,当你想在 Objective-C 和 Core Foundation 之间的对象相互转换时, 都会用 __bridge 关键字。 然而,也有时候,你想要把所有关系给 ARC 或者解除 ARC 的所有关系。 在这种情况下, 那么有两个其他的 bridging casts 关键字:

  • __bridge_transfer: 把所有关系给 ARC
  • __bridge_retained: 收回 ARC 的所有关系

在我们的源文件中,还有一个错误:

return (NSString *)CFURLCreateStringByAddingPercentEscapes(

如果你点击错误消息, 将弹出下面这样的修复提示:

descr

它给你提供了两个可选方案: __bridge 和 __bridge_transfer. 正确的选择是 __bridge_transfer. CFURLCreateStringByAddingPercentEscapes() 函数将创建一个新的 CFStringRef 对象。 当然, 我们要用 NSString, 所以我们需要类型转换。 我们实际要做的是这样:

CFStringRef result = CFURLCreateStringByAddingPercentEscapes(. . .);

NSString *s = (NSString *)result;

return s;

因为函数名称中包含了 “Create”, 它会返回一个被 retain 的对象。 所以我们需要在合适的时候释放它。 如果我没没有把它当做 NSString 返回, 那么我们的代码看起来应该是这样:

- (void)someMethod

{

     CFStringRef result = CFURLCreateStringByAddingPercentEscapes

      (. . .);

     // do something with the string

     // . . .

     CFRelease(result);

}

记住 ARC 仅能作用于 Objective-C 对象, 不能应用于 Core Foundation 对象。 你仍然需要对这样的对象调用 CFRelease() 方法。

我们想要做的是在 escape 方法中将 CFStringRef 对象转换为 NSString 对象, 然后 ARC 会在我们不需要它的时候自动释放掉它。 但是 ARC 需要我们告诉它要这样做。 因此, 我们使用 __bridge_transfer 修饰符告诉它,“嘿 ARC, 这个 CFStringRef 对象现在是 NSString对象了, 并且我想要你来释放它, 这样我们就不需要调用 CFRelease() 了”。

这个方法现在变成这样了:

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

{

  return (__bridge_transfer NSString *)

   CFURLCreateStringByAddingPercentEscapes(

    NULL,

    (__bridge CFStringRef)text,

    NULL,

    CFSTR("!*'();:@&=+$,/?%#[]"),

    CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));

}

我们或得到 CFURLCreateStringByAddingPercentEscapes() 的返回结果, 一个 CFStringRef 对象, 并且将它转换为 NSString, 现在受控于 ARC。

如果我们仅仅应 __bridge 关键字代替, 那么你的应用将会出现内存泄露。 ARC 不知道在你不需要的时候释放这个对象, 并且没有人调用 CFRelease()。 结果就是,这个对象一直留在内存中。 选择合适的 bridge 修饰符非常重要!

为了更简单的记住应该用哪种类型的 bridge, 有一个工具方法 CFBridgingRelease()。 它和 __bridge_transfer 类型转换是一样的效果, 但是它的意思表达的更加清楚。 escape: 的最终版本是这样:

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

{

  return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(

   NULL,

   (__bridge CFStringRef)text,

   NULL,

   CFSTR("!*'();:@&=+$,/?%#[]"),

   CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)));

}

我们将 CFURLCreateStringByAddingPercentEscapes() 方法的调用包装在 CFBridgingRelease() 中, 而不是使用 (__bridge_transfer NSString *)。 CFBridgingRelease() 定义为 inline 函数, 所有不比直接的类型转换慢多少。 它命名为 CFBridgingRelease() 只因为你会在需要用 CFRelease() 来和创建新对象平衡的地方用到它。

另外一个需要 bridging casts 的通用框架是 AddressBook 框架。 例如:

- (NSString *)firstName

{

     return CFBridgingRelease(ABRecordCopyCompositeName(...));

}

所有你调用名称含有 Create,Copy 或 Retain 的 Core Foundation 函数的地方, 你都必须使用 CFBridgingRelease() 来将返回值转移到 ARC 中。

另外一个修饰符 __bridge_retained 又是怎么回事呢? 你要以其他的方式来使用它。 假设你有一个 NSString 并且你需要把它给 Core Foundation API 让他们取得这个字符串对象的所有权。 你不希望 ARC 再去释放这个对象, 因为它会被过度释放, 大多数应用都会崩溃。 另外,使用 __bridge_retained 将这个对象给了 Core Foundation, 这样 ARC 就不再负责释放它了。 例如:

NSString *s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name];

CFStringRef s2 = (__bridge_retained CFStringRef)s1;

// do something with s2

// . . .

CFRelease(s2);

只要 (__bridge_retained CFStringRef) 转换执行了, ARC 就不再不择释放这个字符串对象了。 如果你在这里用了 __bridge, 这个应用很可能会崩溃。 ARC 将在 Core Foundation 使用完这个对象之前释放它。

这种类型的转换也有一个工具方法 CFBridgingRetain()。 从它的名字可以看出, 它让 Core Foundation 对这个对象进行了一次 retain。 上面的例子最好写成这样:

CFStringRef s2 = CFBridgingRetain(s1);

// . . .

CFRelease(s2);

现在,这段代码的意思很明确了。 对 CFRelease() 的调用被 CFBridgingRetain() 方法平衡了。 我怀疑你可能都不会经常用到这种转换。 一下子想想, 我没能想到哪个常用的 API 会需要这样的转换。

注意: 不是所有的 Objective-C 和 Core Foundation 对性爱那个都是 toll-free bridged 的。 例如, CGImage 和 UIImage 就不能互相转换, CGcolor 和 UIColor 也不能。这个页面 列出了不能够互相替换的类型。

__bridge 转换,不仅限于 Core Foundation。 一些API 使用 void * 类型的指针, 可以让你存放任何类型的引用, 不管是 Objective-C 对象,还是 Core Foundation 对象; malloc() 分配的内存, 等等。 void * 表示法的意思是: 这是一个指针, 但是它实际的数据类型可以是任何东西。

如果要将 Objective-C 对象转换成 void * 类型的, 你需要使用 __bridge 转换,例如:

MyClass *myObject = [[MyClass alloc] init];

[UIView beginAnimations:nil context:(__bridge void *)myObject];

在这个的动画的代理方法中, 反向的把这个对象转换回来:

- (void)animationDidStart:(NSString *)animationID

  context:(void *)context

{

     MyClass *myObject = (__bridge MyClass *)context;

     . . .

}

在下一节中,我们将会看到另一个例子, 那里涉及到在 Cocos2D 中使用 ARC。

总结一下:

  • 当把所有关系从 Core Foundation 转换到 Objective-C 的时候, 你要使用 CFBridgingRelease()。
  • 当把所有关系从 Objective-C 转换到 Core Foundation 的时候,你要使用 CFBridgingRetain()。
  • 当你要临时的使用一个类型,而不想要更改所有关系时, 你要使用 __bridge。

这就是 MainViewController 所有要修改的东西。 所有的错误都应该消失了, 现在你可以构建并运行应用了。 这里我没没有涉及到将 AFHTTPRequestOperation 转换到 ARC 。

在以后一段时间,你可能会发现很多你喜欢的第三方库还没有支持 ARC 特性。 维护ARC 和 非ARC 两个版本的库是一件很无趣的事, 所以我希望大多数库的维护者都保持一个版本。 新的库可能会只用 ARC 来写, 但是老的库就很难转换了。 因此, 很可能你的一部分代码是关闭 ARC 的(通过 -fno-objc-arc 编译器标志)。

幸好 ARC 是基于单个文件的, 所以在你开启了 ARC 的项目中结合非 ARC 的代码是没问题的。 因为有时候要为大量的文件禁用 ARC 是比较麻烦的, 我们会在下一节中讨论到一种将非 ARC 第三方库导入项目中的更智能的方式。

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s