ARC 完全指南

ARC 完全指南................................................................................................................3指针保持对象的生命............................................................................................. 3Xcode ARC 自动迁移.........................................................................................9

Xcode 的自动迁移工具...................................................................................9转换后的其它问题........................................................................................ 14禁止某些文件的 ARC .................................................................................... 14ARC 自动迁移的常见问题............................................................................15

属性 property .......................................................................................................17IBOutlet .......................................................................................................... 18readonly property ..........................................................................................19

autoreleasereleaseretain 调用 ..................................................................... 19dealloc 方法..........................................................................................................20AutoreleasePool .................................................................................................... 20Toll-Free Bridging ..................................................................................................21Delegate Weak Property................................................................................26unsafe_unretained ................................................................................................ 27iOS 4 中使用 ARC..................................................................................................27

ARC 高级指南..............................................................................................................28Blocks ARC........................................................................................................28Singleton ARC...................................................................................................34Autorelease AutoreleasePool ..........................................................................40Cocos2D Box2D..............................................................................................45静态库 static library..............................................................................................48最后?................................................................................................................... 49参考资料............................................................................................................... 49

2/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

ARC 完全指南

iOS 5 最显著的变化就是增加了 Automatic Reference Counting(自动引用计数)。ARC 是新 LLVM 3.0 编译器的特性,完全消除了手动内存管理的烦琐。在你的项目中使用 ARC 是非常简单的,所有的编程都和以前一样,除了你不再调用 retain, release, autorelease。启用 ARC之后,编译器会自动在适当的地方插入适当的 retain, release,autorelease 语句。你不再需要担心内存管理,因为编译器为你处理了一切。注意 ARC 是编译器特性,而不是 iOS 运行时特性(除了 weak 指针系统),它也不是其它语言中的垃圾收集器。因此 ARC 和手动内存管理性能是一样的,有些时候还能更加快速,因为编译器还可以执行某些优化。

指针保持对象的生命

ARC 的规则非常简单:只要还有一个变量指向对象,对象就会保持在内存中。当指针指向新值,或者指针不再存在时,相关联的对象就会自动释放。这条规则对于实例变量、synthesize 属性、本地变量都是适用的。

我们可以按所有权(ownership)来考虑 ARC 对象:NSString *firstName = self.textField.text;

firstName 变量成为 NSString 对象的指针,也就是拥有者,该对象保存了文本输入框的内容。

3/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

一个对象可以有多个拥有者,在上面例子中,UITextField text 属性同样也是这个 String 对象的拥有者,也就是有两个指针指向同一个对象:

随后用户改变了输入框的文本,此时 text 属性就指向了新的 String对象。但原来的 String 对象仍然还有一个所有者(firstName 变量),因此会继续保留在内存中。

4/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

只有当 firstName 获得新值,或者超出作用域(本地变量方法返回时、实例变量对象释放时),String 对象不再拥有任何所有者,retain计数降为 0,这时对象会被释放。

我们称 firstName textField.text 指针为“strong”,因为它们能够保持对象的生命。默认所有实例变量和本地变量都是 strong 类型的指针。

另外还有一种“weak”指针,weak 变量仍然指向一个对象,但不是对象的拥有者:

__weak NSString *weakName = self.textField.text;

5/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

weakName 变量和 textField.text 属性都指向一个 String 对象,但weakName 不是拥有者。如果文本框的内容发生变化,则原先的 String对象就没有拥有者,会被释放,此时 weakName 会自动变成 nil,称为“zeroing” weak pointer:

weak 变量自动变为 nil 是非常方便的,这样阻止了 weak 指针继续指向已释放对象。摇摆指针“zombies”会导致非常难于寻找的 Bugzeroing weak pointer 消除了类似的问题。

6/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

weak 指针主要用于-关系,父亲拥有一个儿子的 strong 指针,因此是儿子的所有者;但为了阻止所有权回环,儿子需要使用 weak 指针指向父亲。典型例子是 delegate 模式,你的 View Controller 通过strong 指针拥有一个 UITableView,Table view data source delegate 都是 weak 指针,指向你的 View Controller

注意下面代码是有问题的:

String 对象没有拥有者(weak 指针),在创建之后就会被立即释放。Xcode 会给出警告("Warning: assigning retained object to weakvariable; object will be released after assignment")

变量默认就是__strong 类型的,因此一般我们对于 strong 变量不加__strong 修饰,以下两者是等价的:

__weak NSString *str = [[NSString alloc] initWithFormat:...];

NSLog(@"%@", str); // will output "(null)"

NSString *firstName = self.textField.text;

__strong NSString *firstName = self.textField.text;

7/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

属性可以是 strong weak,写法如下:

有了 ARC,我们的代码可以清晰很多,你不再需要考虑什么时候retain release 对象。唯一需要考虑的是对象之间的关联,也就是哪个对象拥有哪个对象?

以下代码在 ARC 之前是不可能的,在手动内存管理中,从 Array 中移除一个对象会使对象不可用,对象不属于 Array 时会立即被释放。随后 NSLog()打印该对象就会导致应用崩溃。

ARC 中这段代码是完全合法的,因为 obj 变量是一个 strong 指针,它成为了对象的拥有者,从 Array 中移除该对象也不会导致对象被释放。

ARC 也有一些限制。首先 ARC 只能工作于 Objective-C 对象,如果应用使用了 Core Foundation malloc()/free(),此时需要你来管理内存。此外 ARC 还有其它一些更为严格的语言规则,以确保 ARC 能够正常地工作。不过总的来说,ARC 无疑利大于弊!

虽然 ARC 管理了 retain release,但并不表示你完全不需要处理内存管理的问题。因为 strong 指针会保持对象的生命,某些情况下你仍然需要手动设置这些指针为 nil,否则可能导致应用内存不足。无论

@property (nonatomic, strong) NSString *firstName;

@property (nonatomic, weak) id <MyDelegate> delegate;

id obj = [array objectAtIndex:0];

[array removeObjectAtIndex:0];

NSLog(@"%@", obj);

8/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com何时你创建一个新对象时,都需要考虑谁拥有该对象,以及这个对象需

要存活多久。

毫无疑问,ARC Objective-C 的未来!Apple 鼓励开发者将手动内存管理迁移至 ARC,同时新项目也推荐使用 ARCARC 可以产生更简洁的代码,和更健壮的应用。有了 ARC,内存相关的崩溃已经成为过去!

不过现在正处于手动内存向自动内存管理的过渡期,你仍然会经常遇到与 ARC 尚不兼容的代码(你自己的代码或第三方库)。幸运的是,你可以在同一个项目中组合使用 ARC 和非 ARC 代码,同时 Xcode 还提供自动迁移工具。

ARC 还能很好地结合 C++使用,这对游戏开发是非常有帮助的。对于 iOS 4,ARC 有一点点限制(weak pointer system),但也没太大关系。

Xcode ARC 自动迁移要启用一个项目的 ARC,你有以下几种选择:

1. Xcode 带了一个自动转换工具,可以迁移源代码至 ARC
2.
你可以手动转换源文件
3. 你可以在 Xcode 中禁用某些文件使用 ARC,这点对于第三方库非常有用。

Xcode 的自动迁移工具
ARC LLVM 3.0 编译器的特性,而现有工程可能使用老的 GCC 4.2 LLVM-GCC

编译器,因此首先需要设置使用 LLVM 3.0 编译器:9/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.comProject Settings -> target -> Build Settings,在搜索框中输入 compiler,

就可以列出编译器选项设置:

另外最好也选上 Warnings 中的 Other Warning Flags -Wall,这样编译器就会检查所有可能的警告,有助于我们避免潜在的问题。

同样,Build Options 下面的 Run Static Analyzer 选项也最好启用,这样每次 Xcode 编译项目时,都会运行静态代码分析工具来检查我们的代码。

Build Settings 下面,选择“All”,搜索框输入"automatic",可以设置"Objective-C Automatic Reference Counting"选项为 Yes,不过 Xcode 自动转换工具会自动设置这个选项,这里只是告诉你如何手动设置而已。

Xcode ARC 自动转换工具:Edit\Refactor\Convert to Objective-C ARC

10/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

Xcode 会显示一个新窗口,让你选择哪些文件需要转换:

点击 Precheck 按钮,Xcode 可能会提示项目不能转换为 ARC,需要你准备好转换:

11/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

打开 Xcode Preferences 窗口,选择 General 标签,启用 Continue buildingafter errors 选项:

再次执行 Edit\Refactor\Convert to Objective-C ARC,点击 Precheck,Xcode 可能还会提示错误,此时需要你手工修改某些源代码,这些错误是 Xcode无法自动转换的,主要是"toll-free bridged"类型错误,如转换 NSURL CFURLRef 类型,这些转换前面需要加上__bridge__bridge_transfer、或__bridge_retained 关键字

"toll-free bridged"允许 Core Foundation 对象和 Objective-C 对象之间进行转换,但是 ARC 必须知道由谁来负责释放这些对象,因此才增加了__bridge__bridge_transfer__bridge_retained 三个关键字。后面会详细说明它们各自的用法。

12/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

解决了这个问题之后,再次执行 Edit\Refactor\Convert to Objective-C ARC,这时 Xcode 会提示以下窗口:

点击 Continue,几秒钟后,Xcode 会提示所有文件的转换预览,显示源文件的所有改变。左边是修改后的文件,右边是原始文件。在这里你可以一个文件一个文件地查看 Xcode 的修改,以确保 Xcode 没有改错你的源文件:

13/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

转换后的其它问题

最后,Xcode 移除所有 retainreleaseautorelease 调用之后,可能会导致代码出现其它警告、无效语法等,这些都需要你手工进行修改。如:

修改为:

这时候逗号语句中间的部分就不需要了,我们可以直接手动修改为:

注意:Xcode 的自动转换工具最好只使用一次,多次使用可能会出现比较诡异的问题。假如你第一次转换没有转换所有的文件,当你稍后试图再次转换剩余的文件时,Xcode 实际上不会执行任何转换操作。因此最好一次就完成转换,没有转换的文件可以考虑手工进行修改。

禁止某些文件的 ARC

对于某些我们不希望使用 ARC 的文件,例如第三方库源文件,可以在 ProjectSettings -> Build Phases 中,对这些文件选中 -fno-objc-arc 标志。这样Xcode 编译项目时,这些文件就不会使用 ARC

if(fadeOutTimer != nil)

[fadeOutTimer invalidate], [fadeOutTimer release], fadeOutTimer = nil;

if(fadeOutTimer != nil)

[fadeOutTimer invalidate], fadeOutTimer, fadeOutTimer = nil;

if(fadeOutTimer != nil)

[fadeOutTimer invalidate], fadeOutTimer = nil;

14/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

由于开发者很可能不会对所有文件都启用 ARC,Xcode 允许项目结合使用 ARC和非ARC的源文件。你当然可以按上面的设置,对指定文件设置 -fno-objc-arc标志。但最简单的方法是直接使用 Xcode ARC 转换工具,取消选中那些不希望进行ARC转换的源文件,这样Xcode会自动对这些文件设置 -fno-objc-arc标志。如果有许多文件都不使用 ARC,使用这个方法无疑会减少很多时间。

ARC 自动迁移的常见问题
ARC 自动迁移过程中,LLVM 3.0 可能会报出许多错误或警告,你需要手工

进行改变,Xcode 才能继续迁移

"Cast ... requires a bridged cast"

Toll-Free Bridging 对象转换时,需要使用
__bridge, __bridge_transfer, __bridge_retained关键字,使用哪个要看具体情况,后面会专门讲解。

"Receiver type 'X' for instance message is a forward declaration"

.h头文件中,我们经常使用前向声明@class MyView,如果在.m实现文件中,我们没有#import "MyView.h",Xcode 就会报错,加上就好了。

"Switch case is in protected scope"

switch (X)

{

15/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

case Y:

case Y:

NSString *s = ...;

break;

}

类似这样的代码就会报上面错误,ARC 不允许这样的代码,指针变量需要定义在 switch 语句外面,或者使用{}定义一个新的块:
switch (X)
{

}

这样 ARC 才能确定变量的作用域,从而在适当的时候释放该变量。

"A name is referenced outside the NSAutoreleasePool scope that it wasdeclared in"
例如以下代码:

{

NSString *s = ...;

break;

}

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

// . . . do calculations . . .

NSArray* sortedResults =

[[filteredResults

sortedArrayUsingSelector:@selector(compare:)] retain];

[pool release];

return [sortedResults autorelease];

会转换为:

@autoreleasepool

{

}

return sortedResults;

但是这段代码是非法的,因为return sortedResults;超出了变量的作用域。解决办法是将 NSArray* sortedResults;变量定义放在 NSAutoreleasePool 之前,这样 Xcode 就能转换出正常的@autoreleasepool 代码。

// . . . do calculations . . .

NSArray* sortedResults = [filteredResults

sortedArrayUsingSelector:@selector(compare:)];

16/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

"ARC forbids Objective-C objects in structs or unions"

使用 ARC 之后,C Struct 中不能使用 Objective-C 对象,下面代码就是非法的:

typedef struct

{

解决办法是定义一个 Objective-C 类,不使用 C Struct

属性 property

对于.h 头文件,Xcode 主要是将属性定义由 retain 变为 strong,这些属性是类对外的接口,因此定义在.h 文件中:
@property (retain, nonatomic)
变为

@property (strong, nonatomic)

ARC 之前,开发者经常会在.m 实现文件中使用 class extension 来定义private property,如下:

这样做主要是简化实例对象的手动内存管理,让 property setter 方法自动管理原来对象的释放,以及新对象的 retain。但是有了 ARC,这样的代码就不再需要了。一般来说,仅仅为了简化内存管理,是不再需要使用 property 的,虽然你仍然可以这样做,但直接使用实例变量是更好的选择。只有那些属于public 接口的实例变量,才应该定义为 property

我们可以直接在.m 类实现中定义 private 实例变量:

@implementation MainViewController

{

}

我们在使用时,虽然没有定义 property,也可以直接

[self.soundEffect play];

如果你觉得这很别扭,也可以使用

[[self soundEffect] play];

如果你还是觉得应该定义 property,那就定义一个吧,反正也没什么害处。17/ 49

UIImage *selectedImage;

UIImage *disabledImage;

} ButtonImages;

@interface MainViewController ()

@property (nonatomic, retain) NSMutableArray *searchResults;

@property (nonatomic, retain) SoundEffect *soundEffect;

@end

NSOperationQueue *queue;

NSMutableString *currentStringValue;

NSMutableArray *searchResults;

SoundEffect *soundEffect;

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

作为 property 的最佳实践,如果你定义某个东西为 property,则你应该在任何地方都按属性来使用它。唯一例外的是 init 方法、自定义的 getter setter 方法。因此很多时候我们会这样写 synthesize 语句:
@synthesize propertyName = _propertyName;

实际上_propertyName 实例变量甚至可以不定义,编译器会自动为 property定义 "_*" 的实例变量

IBOutlet

ARC 中,所有*outlet*属性都推荐使用 weak,这些 view 对象已经属于 ViewController view hierarchy,不需要再次定义为 strong(ARC 中效果等同于retain)。唯一应该使用 strong outlet File's Owner,连接到 nib 的顶层对象。

outlet 定义为 weak 的优点是简化了 viewDidUnload 方法的实现:

- (void)viewDidUnload

{

}

现在可以简化为:

- (void)viewDidUnload

{}

因为 tableView searchBar 这两个 property 定义为 weak,当它们指向的对象被释放时,这两个变量会自动设置为 nil

iOS App 接收到低内存警告时,View Controller main view 会被 unload,同时会释放所有 subview。这时 UITableView UISearchBar 对象会自动释放,zeroing weak pointer system 就会自动设置 self.tableView self.searchBar nil。因此不需要在 viewDidUnload 中再次设置为 nil,实际上当 viewDidUnload 被调用时,这两个属性已经是 nil 了。

这并不意味着你可以不需要 viewDidUnload,只要你保持一个对象的指针,对象就会存活。当你不需要某个对象时,可以手动设置指针为 nil。如上面示例代码中的 soundEffect = nil; viewDidUnload()方法里面需要设置所有非outlet 变量为 nil,同样还有 didReceiveMemoryWarning()方法。

property 的修饰符总结如下:

[super viewDidUnload];

self.tableView = nil;
self.searchBar = nil;
soundEffect = nil;

[super viewDidUnload];

soundEffect = nil;

18/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

  • strong:等同于"retain",属性成为对象的拥有者

  • weak:属性是 weak pointer,当对象释放时会自动设置为 nil,记住 Outlet

    应该使用 Weak

  • unsafe_unretained:等同于之前的"assign",只有 iOS 4 才应该使用

  • copy:和之前的 copy 一样,复制一个对象并创建 strong 关联

  • assign:对象不能使用 assign,但原始类型(BOOLintfloat)仍然

    可以使用

    readonly property

    ARC 之前,我们可以如下定义一个 readonly property:

    @property (nonatomic, readonly) NSString *result;

    这会隐式地创建一个 assign property,这种用法对于 readonly 值来说是适当的。毕竟你何必对只读数据进行 retain 呢?但上面在 ARC 中会报错:

    你必须显式地使用 strong, weak unsafe_unretained,多数情况下使用strong 是正确的选择:
    @property (nonatomic, strong, readonly) NSString *result;

    对于 readonly property,我们应该总是使用 self.propertyName 来访问实例变量(除了 init 和自定义的 getter setter 方法)。否则直接修改实例变量会混淆 ARC 并导致奇怪的 Bug。正确的方法是使用 class extension 重新定义property readwrite:

    .h 文件:

    .m 文件:

    autoreleasereleaseretain 调用
    对于.m 文件,Xcode 会移除所有的 autoreleaseretainrelease 调用

    修改为

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

unspecified ownership or storage attribute"

@interface WeatherPredictor

@property (nonatomic, strong, readonly) NSNumber *temperature;

@end

@interface WeatherPredictor ()

@property (nonatomic, strong, readwrite) NSNumber *temperature;

@end

self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen

mainScreen] bounds]] autorelease];

19/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen

mainScreen] bounds]];

self.viewController = [[[MainViewController alloc]

initWithNibName:@"MainViewController" bundle:nil] autorelease];

修改为

dealloc 方法

另外启用 ARC 之后,dealloc 方法在大部分时候都不再需要了,因为你不能调用实例对象的 release 方法,也不能调用[super dealloc]。假如原先的dealloc 方法只是释放这些对象,Xcode 就会把 dealloc 方法完全移除。你不再需要手动释放任何实例变量。

如果你的 dealloc 方法处理了其它资源(非内存)的释放,如定时器、CoreFoundation 对象,则你仍然需要在 dealloc 方法中进行手动释放,如 CFRelease(),free()等。这时 Xcode 会保留 dealloc 方法,但是移除所有的 release [superdealloc]调用。如下:

- (void)dealloc

{

}

AutoreleasePool

ARC 仍然保留了 AutoreleasePool,但是采用了新的 Block 语法,于是我们的 main 函数会如下修改:

会修改变:

}

self.viewController = [[MainViewController alloc]

initWithNibName:@"MainViewController" bundle:nil];

AudioServicesDisposeSystemSoundID(soundID);

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

int retVal = UIApplicationMain(argc, argv, nil,

NSStringFromClass([AppDelegate class]));

[pool release];
return retVal;
@autoreleasepool {

int retVal = UIApplicationMain(argc, argv, nil,

NSStringFromClass([AppDelegate class]));

return retVal;

20/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.comToll-Free Bridging

当你在 Objective-C Core Foundation 对象之间进行转换时,就需要使用Bridge cast

今天的多数应用很少需要使用 Core Foundation,大多数工作都可以直接使用 Objective-C 类来完成。但是某些底层 API,如 Core Graphics Core Text,都基于 Core Foundation,而且不太可能会有 Objective-C 的版本。幸运的是,iOS 的设计使得这两种类型的对象非常容易转换。

例如 NSString CFStringRef 就可以同等对待,在任何地方都可以互换使用,背后的设计就是 toll-free bridging。在 ARC 之前,只需要使用一个简单的强制类型转换即可:

当然,alloc 分配了 NSString 对象,你需要在使用完之后进行释放,注意是释放转换后的 CFStringRef 对象:
CFRelease(s1);

反过来,从 Core Foundation Objective-C 的方向也类似:

现在我们使用了 ARC,情况变得不一样!以下代码在手动内存管理中是完全合法的,但在 ARC 中却存在问题:
- (NSString *)escape:(NSString *)text
{

CFStringRef s1 = (CFStringRef) [[NSString alloc]

initWithFormat:@"Hello, %@!", name];

CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, bytes,

kCFStringEncodingMacRoman);

NSString *s3 = (NSString *)s2;

// release the object when you're done

[s3 release];

return [(NSString *)CFURLCreateStringByAddingPercentEscapes(

NULL,

(CFStringRef)text,

NULL,

(CFStringRef)@"!*'();:@&=+$,/?%#[]", // 这里不需要 bridging

casts,因为这是一个常量,不需要释放!

CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncodin

g))

autorelease];

}

首先需要移除 autorelease 调用。然后编译器还会报两个类型转换错误:21/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

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

错误分别来自以下两行代码:

编译器必须知道由谁来负责释放转换后的对象,如果你把一个 NSObject 当作 Core Foundation 对象来使用,则 ARC 将不再负责释放该对象。但你必须明确地告诉 ARC 你的这个意图,编译器没办法自己做主。同样如果你创建一个 CoreFoundation 对象并把它转换为 NSObject 对象,你也必须告诉 ARC 占据对象的所有权,并在适当的时候释放该对象。这就是所谓的 bridging casts

CFURLCreateStringByAddingPercentEscapes()函数的参数需要两个CFStringRef 对象,其中常量 NSString 可以直接转换,因为不需要进行对象释放;但是 text 参数不一样,它是传递进来的一个 NSString 对象。而函数参数和局部变量一样,都是 strong 指针,这种对象在函数入口处会被 retain,并且对象会持续存在直到指针被销毁(这里也就是函数返回时)。

对于 text 参数,我们希望 ARC 保持这个变量的所有权,同时又希望临时将它当作 CFStringRef 对象来使用。这种情况下可以使用__bridge 说明符,它告诉 ARC 不要更改对象的所有权,按普通规则释放该对象即可。

多数情况下,Objective-C 对象和 Core Foundation 对象之间互相转换时,我们都应该使用__bridge。但是有时候我们确实需要给予 ARC 某个对象的所有权,或者解除 ARC 对某个对象的所有权。这种情况下我们就需要使用另外两种bridging casts:

  • __bridge_transfer:给予 ARC 所有权

  • __bridge_retained:解除 ARC 所有权

    在上面代码中,"return (NSString*)CFURLCreateStringByAddingPercentEscapes",编译器弹出的修复提示有两个:

(NSString *)CFURLCreateStringByAddingPercentEscapes(...)

(CFStringRef)text

22/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

两个解决办法:__bridge __bridge_transfer,正确的选择应该是 __bridge_transfer

因为 CFURLCreateStringByAddingPercentEscapes() 函数创建了一个新的 CFStringRef 对象,当然我们要的是 NSString 对象,因此我们使用了强制转换。实际上我们真正想要做的是:

CFURLCreateStringByAddingPercentEscapes 函数的 create 可以看出,函数会返回一个 retain 过的对象。某个人需要负责在适当的时候释放该对象,如果我们不把这个对象返回为 NSString,则通常我们需要自己调用:

{

}

不过 ARC 只能作用于 Objective-C 对象,不能释放 Core Foundation 对象。因此这里你仍然需要调用 CFRelease()来释放该对象。

这里我们的真实意图是:转换新创建的 CFStringRef 对象为 NSString 对象,并且当我们不再需要使用这个 NSString 对象时,ARC 能够适当地释放它。

因此我们使用 __bridge_transfer告诉ARC:"嘿!ARC,这个CFStringRef对象现在是一个 NSString 对象了,我希望你来销毁它,我这里就不调用CFRelease()来释放它了"

CFStringRef result = CFURLCreateStringByAddingPercentEscapes(. . .);

NSString *s = (NSString *)result;

return s;

CFRelease(result);

- (void)someMethod

CFStringRef result = CFURLCreateStringByAddingPercentEscapes(. . .);

// do something with the string

// . . .

CFRelease(result);

23/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

如果我们使用 __bridge,就会导致内存泄漏。ARC并不知道自己应该在使用完对象之后释放该对象,也没有人调用 CFRelease()。结果这个对象就会永远保留在内存中。因此选择正确的 bridge 说明符是至关重要的。

为了代码更加可读和容易理解,iOS 还提供了一个辅助函数:CFBridgingRelease()。函数所做事情和 __bridge_transfer 强制转换完全一样,但更加简洁和清晰。CFBridgingRelease() 函数定义为内联函数,因此不会导致额外的开销。函数之所以命名为 CFBridgingRelease(),是因为一般你会在需要使用 CFRelease()释放对象的地方,调用 CFBridgingRelease()来传递对象的所有权。

因此最后我们的代码如下:

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

{

}

另一个常见的需要 CFBridgingRelease 的情况是 AddressBook framework:

- (NSString *)firstName

{

}

只要你调用命名为 Create, Copy, Retain Core Foundation 函数,你都需要使用 CFBridgingRelease()安全地将值传递给ARC

__bridge_retained 则正好相反,假设你有一个 NSString 对象,并且要将它传递给某个 Core Foundation API,该函数希望接收这个 string 对象的所有权。这时候你就不希望 ARC 也去释放该对象,否则就会对同一对象释放两次,而且必将导致应用崩溃!换句话说,使用 __bridge_retained 将对象的所有权给予 Core Foundation,而 ARC 不再负责释放该对象。

如下面例子所示:

return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(

NULL,

(__bridge CFStringRef)text,

NULL,

g)));

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

CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncodin

return CFBridgingRelease(ABRecordCopyCompositeName(...));

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

CFStringRef s2 = (__bridge_retained CFStringRef)s1;

// do something with s2

// . . .

CFRelease(s2);

24/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

一旦 (__bridge_retained CFStringRef) 转换完成,ARC 就不再负责释放该对象。如果你在这里使用 __bridge,应用就很可能会崩溃。ARC 可能在 CoreFoundation 正在使用该对象时,释放掉它。

同样__bridge_retained 也有一个辅助函数:CFBridgingRetain()。从名字就可以看出,这个函数会让 Core Foundation 执行 retain,实际如下:

现在你应该明白了,上面例子的 CFRelease()是和 CFBridgingRetain()对应的。你应该很少需要使用__bridge_retained CFBridgingRetain()

__bridge 转换不仅仅局限于 Core Foundation 对象,某些 API 使用 void *指针作为参数,允许你传递任何东西的引用:Objective-C 对象、Core Foundation对象、malloc()内存缓冲区等等。void *表示这是一个指针,但实际的数据类型可以是任何东西!

要将 Objective-C 对象和 void *互相转换,你也需要使用__bridge 转换,如下:

animation delegate 方法中,你再将对象强制转回来:

{}

总结:

  • 使用 CFBridgingRelease(),从 Core Foundation 传递所有权给Objective-C;

  • 使用 CFBridgingRetain(),从 Objective-C 传递所有权给 CoreFoundation;

  • 使用__brideg,表示临时使用某种类型,不改变对象的所有权。

CFStringRef s2 = CFBridgingRetain(s1);

// . . .

CFRelease(s2);

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

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

- (void)animationDidStart:(NSString *)animationID

context:(void *)context

MyClass *myObject = (__bridge MyClass *)context;

...

25/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.comDelegate Weak Property

使用 Delegate 模式时,通常我们会使用 weak property 来引用 delegate,这样可以避免所有权回环。

你可能已经熟悉 retain 回环的概念,两个对象互相 retain 时,会导致两个对象都无法被释放,这也是内存泄漏的常见原因之一。垃圾收集器(GC)可以识别并处理 retain 回环的情况,但 ARC 不是 GC,没办法处理所有权回环,因此需要你使用 weak 指针来避免。

在上面代码中,MainViewController 创建一个 DetailViewController,并调用 presentViewController view 呈现出来,从而拥有了一个 strong 指针指向创建的 DetailViewController 对象。反过来,DetailViewController 也通过delegate 拥有了一个指向 MainViewController weak 指针。

MainViewController 调用 dismissViewControllerAnimated:时,就会自动失去 DetailViewController strong 引用,这时候 DetailViewController对象就会被自动释放。

如果这两个指针都是 strong 类型,就会出现所有权回环。导致对象无法在适当的时候被释放。

DetailViewController *controller = [[DetailViewController alloc]

initWithNibName:@"DetailViewController" bundle:nil];

controller.delegate = self;

[self presentViewController:controller animated:YES

completion:nil];

26/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.comunsafe_unretained

除了 strong weak,还有另外一个 unsafe_unretained 关键字,一般你不会使用到它。声明为 unsafe_unretained 的变量或 property,编译器不会为其自动添加 retain releaseunsafe_unretained 只是为了兼容 iOS 4,因为 iOS4 没有 weak pointer system

"unsafe"表示这种类型的指针可能指向不存在的对象。使用这样的指针很可能导致应用崩溃,启用 NSZombieEnabled 调试工具可以查找这种类型的错误。

运行应用并模拟内存不足警告时,可以看到以下输出:

应用崩溃了!unsafe_unretained 指针并没有对象的所有权。意味着UITableView 不会被这个指针保持生命,在 viewDidUnload 被调用之前UITableView 就已经被释放(UITableView 唯一的拥有者是 main view)。如果这是一个真正的 weak 指针,则它的值会被自动设置为 nil,这也正是"zeroing"weak pointer 的优点。

unsafe_unretained 指针和 weak 指针不一样的是,当相关联的对象释放时,指针不会被设置为 nil,因此它实际上指向不存在的对象。有时候你的应用看上去没有问题,但更大的可能是应用会崩溃。

注意:你需要选择 Product->Edit Scheme->Diagnostics,并启用 zombies调试选项,才能立即看到应用的崩溃。

iOS 4 中使用 ARC

ARC 主要是 LLVM 3.0 编译器(而不是 iOS 5)的新特性,因此你也可以在 iOS4.0之后的系统中使用ARC,不过需要注意的是,weak 指针需要iOS5才能使用。如果你要在 iOS 4 中部署 ARC 应用,你就不能使用 weak property __weak 变量。

实际上,要让 ARC 正确运行于 iOS 4 平台,你不需要做任何额外的事情。只要你选择 iOS 4 作为应用的 Deployment Target,编译器就会自动插入一个兼容库到你的工程,使得 ARC 功能在 iOS 4 平台中可用。

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

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

Artists[982:207] Received memory warning.

Artists[982:207] *** -[UITableView retain]: message sent to deallocated

instance 0x7033200

27/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com这时如果你使用了 weak 引用,编译器会给出如下错误:

既然 iOS 4 不能使用 __weak weak,你就得分别替换
__unsafe_unretained unsafe_unretained,记住这些变量在对象被释放时不会自动设置为 nil,因此如果你不够小心,你的变量就可能会指向不存在的对象。启用 NSZombieEnabled 来调试这种类型的错误吧!

ARC 高级指南Blocks ARC

Block ARC 可以很好地工作,实际上 ARC 使得 Block 比以前更容易使用。Block 在堆栈中初始创建,如果你需要在超出当前作用域之后,仍然保持 Block继续存在,就必须使用 [copy] Block_copy() Block 复制到堆。现在 ARC自动处理了这些复制工作,不过和以前相比,Block 的使用也有一些地方变得不同了。

假如我们有一个 AnimatedView,每秒钟会重新绘制自己几次。我们使用 Block来提供具体的绘制操作。
#import <UIKit/UIKit.h>

"Error: the current deployment target does not support automated __weak

references"

typedef void (^AnimatedViewBlock)(CGContextRef context, CGRect rect,

CFTimeInterval totalTime, CFTimeInterval deltaTime);

@interface AnimatedView : UIView

@property (nonatomic, copy) AnimatedViewBlock block;

@end

注意 Block 使用了 copy 关键字,使用 strong 会直接导致应用崩溃,因为Block 在堆栈中创建,必须 copy property(堆)。

这个类只有一个 block 属性,block 有四个参数:context, rect, totalTimedeltaTime。其中 context rect 是绘制用途,两个时间参数用来确定动画的步骤

@implementation AnimatedView

{

#import <QuartzCore/QuartzCore.h>

#import "AnimatedView.h"

NSTimer *timer;

28/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

CFTimeInterval startTime;

CFTimeInterval lastTime;

}

@synthesize block;
- (id)initWithCoder:(NSCoder *)aDecoder

{

if ((self = [super initWithCoder:aDecoder]))

{

timer = [NSTimer scheduledTimerWithTimeInterval:0.1

target:self

selector:@selector(handleTimer:)

userInfo:nil
repeats:YES];

startTime = lastTime = CACurrentMediaTime();

}

return self;

}

- (void)dealloc

{}

- (void)handleTimer:(NSTimer*)timer

{

}

- (void)drawRect:(CGRect)rect

{

NSLog(@"dealloc AnimatedView");

[timer invalidate];

[self setNeedsDisplay];

CFTimeInterval now = CACurrentMediaTime();

CFTimeInterval totalTime = now - startTime;

CFTimeInterval deltaTime = now - lastTime;

lastTime = now;
if (self.block != nil)

deltaTime);

self.block(UIGraphicsGetCurrentContext(), rect, totalTime,

}

@end

29/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

这个实现看上去没有问题,但实际上我们已经产生了所有权回环。在initWithCoder 中我们创建了一个定时器,并且在 dealloc 中停止了定时器。不过假如你运行App,你会发现 AnimatedViewdealloc方法永远不会被调用到。

NSTimer 显然保持了一个 strong 引用,指向自己的 target,这里也就
AnimatedView 对象。而 AnimatedView 同时又有一个 strong 引用指向NSTimer。除非我们显式地释放这些对象,否则永远不会被释放。

你可能会觉得以下代码可以解决问题:

@implementation AnimatedView

{

}

但是简单地将 timer 定义为 __weak 并不能解决所有权回环,AnimatedView不再是 NSTimer 对象的拥有者,但是 NSTimer 仍然被其它对象所拥有(run loop),而且由于 timer 仍然拥有 AnimatedView strong 引用,它还是会一直保持AnimatedView 存在,除非 invalidate 定时器,否则 AnimatedView 对象永远无法被释放。

这里我们可以定义一个 stopAnimation方法来打破retain回环,修改dealloc stopAnimation 方法如下:
- (void)stopAnimation
{

[timer invalidate], timer = nil;

}

- (void)dealloc

{

}

AnimatedView 被释放之前,使用这个类的用户(例如DetailViewController)应该先调用 stopAnimaTion 来停止定时器。

下面我们看看 DetailViewController.m中如何提供一个Block来处理Drawing,代码如下:
- (void)viewDidLoad
{

__weak NSTimer *timer;

CFTimeInterval startTime;

CFTimeInterval lastTime;

NSLog(@"dealloc AnimatedView");

[super viewDidLoad];

self.navigationBar.topItem.title = self.artistName;

UIFont *font = [UIFont boldSystemFontOfSize:24.0f];

CGSize textSize = [self.artistName sizeWithFont:font];

30/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

self.animatedView.block = ^(CGContextRef context, CGRect

rect, CFTimeInterval totalTime, CFTimeInterval deltaTime)

{

NSLog(@"totalTime %f, deltaTime %f", totalTime, deltaTime);

CGPoint textPoint = CGPointMake((rect.size.width

- textSize.width)/2, (rect.size.height - textSize.height)/2);

[self.artistName drawAtPoint:textPoint withFont:font];

};

}

Block 外面,我们创建了一个 UIFont 对象,并计算了 text 绘制所需的大小。在 Block 中,我们使用 textSize 定位文本到矩形的中央并进行绘制。NSLog则用来显示这个 Block 会定时被调用。

运行 App,看上去好像没有任何问题。但是如果你查看 Debug 输出,你会发现最终 AnimatedView DetailViewController 都无法被释放!

如果你以前使用过 Block,你应该已经知道 Block 会捕获(capture)自己使用到的所有变量。如果这些变量是指针,Block 会对指向的每个对象执行一次retain。在上面例子中,Block retain self,也就是 DetailViewController(因为我们在 Block 中使用了 self.artistName)。现在即使关闭了DetailViewController,也不会释放 DetailViewController 对象,因为 Block保持了它的引用。

另外定时器也会继续运行,因为我们没有调用 stopAnimation 来中止定时器(DetailViewController dealloc 没有被调用)。

简单的解决办法是不要在 Block 中使用 self,这意味着你不能在 Block 中使用任何 property、实例变量(因为实例变量会使用 self->ivar)、方法(method)。而只能使用局部变量,先把要使用的对象保存在局部变量中,然后在 Block 中使用这个局部变量:

{

这样就可以了,Block 只使用了局部变量,没有引用 self,因此 Block 不会捕获 DetailViewController 对象,后者在 View 关闭时能够自动被释放。

但很多时候你不能避免在 Block 中使用 self,在 ARC 以前,你可以使用以下技巧:

NSString *text = self.artistName;

self.animatedView.block = ^(CGContextRef context, CGRect rect,

CFTimeInterval totalTime, CFTimeInterval deltaTime)

CGPoint textPoint = CGPointMake((rect.size.width - textSize.width)/2,

(rect.size.height - textSize.height)/2);

[text drawAtPoint:textPoint withFont:font];

};

31/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

__block DetailViewController *blockSelf = self;

self.animatedView.block = ^(CGContextRef context, CGRect

rect, CFTimeInterval totalTime, CFTimeInterval deltaTime)

{

__block 关键字表示 Block retain 这个变量,因此可以使用blockSelf.artistName 来访问 artistName 属性,而 Block 也不会捕获 self 对象。

不过 ARC 中不能使用这个方法,因为变量默认是 strong 引用,即使标记为__block 也仍然是 strong 类型的引用。这时候__block 的唯一功能是允许你修改已捕获的变量(没有__block 则变量是只读的)。

ARC 的解决办法是使用 __weak 变量:

{

...

[blockSelf.artistName drawAtPoint:textPoint withFont:font];

};

__weak DetailViewController *weakSelf = self;

self.animatedView.block = ^(CGContextRef context, CGRect

rect, CFTimeInterval totalTime, CFTimeInterval deltaTime)

DetailViewController *strongSelf = weakSelf;

if (strongSelf != nil)

{

CGPoint textPoint = CGPointMake((rect.size.width

- textSize.width)/2, (rect.size.height - textSize.height)/2);

};

}

[strongSelf.artistName drawAtPoint:textPoint withFont:font];

weakSelf 变量引用了 self,但不会进行 retain。我们让 Block 捕获 weakSelf而不是 self,因此不存在所有权回环。但是我们在 Block 中不能直接使用weakSelf,因为这是一个 weak 指针,当 DetailViewController 释放时它会自动变成 nil。虽然向 nil 发送 message 是合法的,我们在 Block 中仍然检查了对象是否存在。这里还有一个技巧,我们临时把 weakSelf 转换为 strong 类型的引用 strongSelf,这样我们在使用 strongSelf 的时候,可以确保DetailViewController 不会被其它人释放掉!

对于简单的应用,这人技巧可能没太大必要,我们可以直接使用 weakSelf。因为 AnimatedView 属于 controller view hierarchy,DetailViewController不可能在 AnimatedView 之前被释放。

但是假如 Block 被异步使用,则创建 strong 引用来保持对象存活就是必要的。此外,如果我们直接使用 artistName 实例变量,可能会编写如下代码:__weak DetailViewController *weakSelf = self;

32/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

self.animatedView.block = ^(CGContextRef context, CGRect

rect, CFTimeInterval totalTime, CFTimeInterval deltaTime)

{

DetailViewController 被释放之前,这段代码都能正常工作,一旦DetailViewController 被释放,nil->artistName 肯定会导致应用崩溃!

因此,如果你需要在 ARC 环境中使用 Block,并且想避免捕获 self,推荐采用如下代码模式:

{

};

[weakSelf->artistName drawAtPoint:textPoint withFont:font];

};

__weak id weakSelf = self;

block = ^()

id strongSelf = weakSelf;

if (strongSelf != nil)

{

// do stuff with strongSelf

}

如果目标机器是 iOS 4,就不能使用__weak,可以如下修改:

__block __unsafe_unretained id unsafeSelf = self;

注意在这种情况下,你无法确定 unsafeSelf 是否仍然指向合法的对象。你需要采取额外的步骤来保护,否则就会面临 zombies!

整体来说,编写 ARC Block 代码和之前是一样的,除了你不再调用 retainrelease。但是仍然可能会引入微妙的 bug,因为没有显式的 retain 就意味着Block 不会再捕获某个对象,因此当你使用的时候对象可能已经不存在。

假设有个 DelayedOperation 类,它等待"delay"秒,然后执行 block。在 block中你会 autorelease 释放掉 DelayedOperation 实例。由于 block 捕获了"operation"实例,从而保持了对象的生命,下面代码模式在 ARC 之前是可以正常工作的:

{

DelayedOperation *operation = [[DelayedOperation alloc] initWithDelay:5

block:^

}];

NSLog(@"Performing operation");

// do stuff

[operation autorelease];

但在 ARC 中,你不允许调用 autorelease,因此代码变成如下:

DelayedOperation *operation = [[DelayedOperation alloc] initWithDelay:5

block:^

33/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com{

猜猜会发生什么?block 永远不会被执行到。DelayedOperation 实例在创建后立即就会被释放,因为没有人保持对象的生命。解决办法是让 block 捕获operation 实例对象,并在完成任务之后设置为 nil,从而释放该 operation 对象:

{

现在 block 会保持对象存活,注意"opoeration"变量必须声明为__block,因为我们要在 block 里面修改它为 nil

Singleton ARC

如果你的应用使用了 Singleton,你的实现可能包含以下方法:

+ (id)allocWithZone:(NSZone *)zone

{

}

- (id)copyWithZone:(NSZone *)zone

{

}

- (id)retain

{

}

- (NSUInteger)retainCount

{

}

- (oneway void)release

{

}

- (id)autorelease

{

NSLog(@"Performing operation");

// do stuff

}];

__block DelayedOperation *operation = [[DelayedOperation

alloc] initWithDelay:5 block:^

NSLog(@"Performing operation");

}];

// do stuff

operation = nil;

return [[self sharedInstance] retain];

return self;

return self;

return NSUIntegerMax;

// empty

34/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.comreturn self;

}

这是典型的 singleton 实现模式,retain release 都覆盖掉,使其不能创建多个实例对象。毕竟 Singleton 就是为了只创建一个全局对象。

ARC 环境下,上面代码无法工作。你不能调用 retain release,也不允许你覆盖这些方法。

按我的看法,无论如何,上面这个都不是非常有用的模式。你确定需要只有一个实例的对象吗?我倾向于使用一个称为"Interesting Instance Pattern"Singleton 模式。这也是 Apple 在它们的 API 中采用的模式。通常你通过一个sharedInstance defaultInstance 类方法来访问这个 preferred 实例,但只要你愿意,你也可以创建你自己的实例。

对于 iOS API 来说,这些类能够让你创建自己的实例实际上是一个特性,例如 NSNotificationCenter

下面我们使用 GradientFactory 类来演示实现 Singleton 的首选方式,GradientFactory.h 如下:
@interface GradientFactory : NSObject

+ (id)sharedInstance;

这个类有一个 sharedInstance类方法,用来访问preferred实例,同时还有一个 newGradient方法返回一个CGGradientRef对象。你仍然可以[[alloc] init] 你自己的 GradientFactory 实例,不过按照惯例你不应该这样做。

GradientFactory.m 中如下实现 sharedInstance 方法:

+ (id)sharedInstance

{

}

- (CGGradientRef)newGradientWithColor1:(UIColor *)color1

color2:(UIColor *)color2 color3:(UIColor *)color3

midpoint:(CGFloat)midpoint;

@end

static GradientFactory *sharedInstance;

if (sharedInstance == nil)

{

}

sharedInstance = [[GradientFactory alloc] init];

return sharedInstance;

35/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

这就是你实现 Singleton 需要做的全部,sharedInstance 方法使用 staticlocal 变量来跟踪实例是否已经存在,如果不存在就创建一个。注意我们不需要显式地设置变量为 nil:
static GradientFactory *sharedInstance = nil;

ARC 中,所有指针变量默认都是 nil,在 ARC 之前,只有实例变量才会默认为 nil。如果你编写下面代码:
- (void)myMethod
{

}

编译器会报怨:"Variable is uninitialized when used here",而输出则是随机数值:

但在 ARC 中,输出则如下:

int 仍然是随机值(这样使用编译器也会警告),但 someString 的初始值已经是 nil,这样的优点是指针永远不会指向非法对象。

继续实现 GradientFactory 的下面方法:

{

int someNumber;

NSLog(@"Number: %d", someNumber);

NSString *someString;

NSLog(@"String: %p", someString);

Woot[2186:207] Number: 67

Woot[2186:207] String: 0x4babb5

Artists[2227:207] Number: 10120117

Artists[2227:207] String: 0x0

- (CGGradientRef)newGradientWithColor1:(UIColor *)color1

color2:(UIColor *)color2 color3:(UIColor *)color3

midpoint:(CGFloat)midpoint;

NSArray *colors = [NSArray

arrayWithObjects:(id)color1.CGColor, (id)color2.CGColor,

(id)color3.CGColor, nil];

const CGFloat locations[3] = { 0.0f, midpoint, 1.0f };

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

CGGradientRef gradient =

CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors,

locations);

CGColorSpaceRelease(colorSpace);

return gradient;

}

36/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.comCGGradientCreateWithColors()函数的参数是:一个 CGColorSpaceRef 对象、

CGColorRef 对象的数组、CGFloats 数组。

其中 NSArray *colors 使用 ARC __bridge,这样才能传递给CGGradientCreateWithColors()函数使用。这里我们让 ARC 继续保持 Array 对象的所有权,并负责释放对象。不过 NSArray 只能存储 Objective-C 对象,因此这里需要将 Core Foundation CGColorRef 转换为 id,因为所有 Core Foundation对象类型都与 NSObject 能够 toll-free bridged。但是不能转换为 UIColor,因为 CGColor UIColor 并不是 toll-free bridged。混合使用这两种不同类型的framework 体系架构时,确实够乱的。

另外 Array 中的 CGColorRef 对象转换为 id 时并不需要使用 __bridge,因为编译器知道这里的规则,只要数组还存在,就会自动 retain 这些 color 对象。

最后返回一个新创建的 CGGradientRef 对象,注意调用方负责释放这个gradient 对象,它是一个 Core Foundation 对象,因此 ARC 不会进行处理。调用方需要调用 CGGradientRelease()来释放这个对象,否则应用就会产生内存泄漏。

根据 Cocoa 命名规则,方法名中带有 alloc, init, new, copy, mutableCopy的方法会传递返回对象的所有权给调用方,我们的 newGradient 方法也一样。老实说,如果你的所有代码都基于 ARC,这个命名惯例已经变得完全不再重要。编译器能够为你处理一切对象释放的工作,不过通过名字让其它人知道你的方法会创建对象并传递所有权,要求调用方释放对象,仍然是一种很好的做法。

假设你有一个方法在非 ARC 源文件中,并命名为 newWidget,它返回一个autoreleased 而不是 retained 字符串。如果你在 ARC 环境中使用这个方法,ARC最后会 release 这个返回的对象,从而多次 release 导致应用崩溃。因此最好重命名你的方法为 createWidget makeWidget,让 ARC 知道不需要 release 该对象(或者如果你不能修改方法名,可以使用 NS_RETURNS_NOT_RETAINED NS_RETURNS_RETAINED annotation 告诉编译器这个方法是非标准的)。

混合使用 Core Foundation Objective-C 对象时,你必须非常小心。试着找出下面代码的 bug:

CGColorRef cgColor1 = [[UIColor alloc] initWithRed:1 green:0

blue:0 alpha:1].CGColor;

CGColorRef cgColor2 = [[UIColor alloc] initWithRed:0 green:1

blue:0 alpha:1].CGColor;

CGColorRef cgColor3 = [[UIColor alloc] initWithRed:0 green:0

blue:1 alpha:1].CGColor;

NSArray *colors = [NSArray arrayWithObjects:(__bridge

id)cgColor1, (__bridge id)cgColor2, (__bridge id)cgColor3, nil];

37/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

如果你这样写代码,应用肯定会崩溃,因为你创建了三个 retain 而不是autorelease UIColor 对象,只要没有 strong 指针引用它们,这些对象就会被释放。由于CGColorRef不是Objective-C对象,cgColor1cgColor2 cgColor3变量都不是 strong 指针,创建的 UIColor 都会立即被释放,而这些指针现在都指向垃圾内存。下面这样做则是正确的:

现在我们分配 UIColor 对象并返回一个 autorelease 的对象,对象在autorelease pool flush 之前都会存在。如果你不希望操心这些恶心的问题,那就尽量不要使用 Core Foundation 框架吧。

下面是这个 Singleton GradientFactory 的使用示例,DetailViewController viewDidLoad 方法如下:
- (void)viewDidLoad
{

CGColorRef cgColor1 = [UIColor colorWithRed:1 green:0 blue:0

alpha:1].CGColor;
[super viewDidLoad];

self.navigationBar.topItem.title = self.artistName;

UIFont *font = [UIFont boldSystemFontOfSize:24.0f];

CGSize textSize = [self.artistName sizeWithFont:font];

float components[9];

NSUInteger length = [self.artistName length];

NSString* lowercase = [self.artistName lowercaseString];

for (int t = 0; t < 9; ++t)

{

}

unichar c = [lowercase characterAtIndex:t % length];

components[t] = ((c * (10 - t)) & 0xFF) / 255.0f;

UIColor *color1 = [UIColor

colorWithRed:components[0] green:components[3] blue:components[6]

alpha:1.0f];

UIColor *color2 = [UIColor

colorWithRed:components[1] green:components[4] blue:components[7]

alpha:1.0f];

UIColor *color3 = [UIColor

colorWithRed:components[2] green:components[5] blue:components[8]

alpha:1.0f];

__weak DetailViewController *weakSelf = self;

38/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

self.animatedView.block = ^(CGContextRef context, CGRect

rect, CFTimeInterval totalTime, CFTimeInterval deltaTime)

{

DetailViewController *strongSelf = weakSelf;

if (strongSelf != nil)

{

CGPoint startPoint = CGPointMake(0.0, 0.0);

CGPoint endPoint = CGPointMake(0.0, rect.size.height);

CGFloat midpoint = 0.5f + (sinf(totalTime))/2.0f;

CGGradientRef gradient = [[GradientFactory sharedInstance]

newGradientWithColor1:color1 color2:color2

color3:color3
midpoint:midpoint];

CGContextDrawLinearGradient(context, gradient,

startPoint, endPoint,

kCGGradientDrawsBeforeStartLocation |

kCGGradientDrawsAfterEndLocation);

CGGradientRelease(gradient); // Core Foundation 内存仍然需

要手动内存管理

CGPoint textPoint = CGPointMake(

(rect.size.width - textSize.width)/2,

(rect.size.height - textSize.height)/2);

[strongSelf.artistName

drawAtPoint:textPoint withFont:font];

};

}

}

如果 Singleton 需要使用在多线程环境中,上面简单的 sharedInstance 方法就不能满足要求了。健壮的实现方法如下:
+ (id)sharedInstance
{

}

static GradientFactory *sharedInstance;

static dispatch_once_t done;

dispatch_once(&done, ^{

sharedInstance = [[GradientFactory alloc] init];

});

return sharedInstance;

39/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com即使多个线程同时执行这个 block,Grand Central Dispatch 库的

dispatch_once()函数也能确保 alloc init 只会被执行一次。

Autorelease AutoreleasePool
ARC 仍然使用了 autorelease autorelease pool,不过现在这个特性是语

言结构而不是以前的类(现在是@autoreleasepool)。

ARC 中,方法名如果以 alloc, init, new, copy, mutableCopy 开头,就是返回 retain 的对象,其它方法全部返回 autorelease 的对象。这条规则实际上与手动内存管理是一样的。因为 ARC 代码需要与非 ARC 协同工作。

retain 的对象在没有变量指向它时就会立即释放,但 autorelease 对象则是在 autorelease pool 排干(drain)时才会被释放。在 ARC 之前,你需要调用NSAutoreleasePool 对象的 [drain] [release] 方法,现在则是直接在@autoreleasepool 块退出时进行 drain:
@autoreleasepool
{

但是如果你像下面这样编写代码,则即使 NSString 对象在@autoreleasepool块中创建,stringWithFormat:方法也确实返回了一个 autorelease 对象,但变量 s strong 类型的,只要 s 没有退出作用域,string 对象就会一直存在:

{

}

// the string object is still alive here

使用 __autoreleasing 可以使 autorelease pool 释放掉该对象,它告诉编译器这个变量指向的对象可以被 autorelease,此时变量不是 strong 指针,string 对象会在@autoreleasepool 块的末尾被释放。不过注意变量在对象释放后,仍然会继续指向一个死掉的对象。如果你继续使用它,应用就会崩溃。代码如下:

{

}

NSString *s = [NSString stringWithFormat:. . .];

} // the string object is deallocated here

NSString *s;

@autoreleasepool

s = [NSString stringWithFormat:. . .];

__autoreleasing NSString *s;

@autoreleasepool

s = [NSString stringWithFormat:. . .];

// the string object is deallocated here

NSLog(@"%@", s); // crash!

40/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

你应该很少需要使用 __autoreleasing,这个关键字主要用在输出参数(out-parameters)或按引用传递(pass-by-reference),典型的例子就是使用了 (NSError **) 参数的那些方法。

我们来看一个例子:

- (IBAction)coolAction

{

NSData *data = UIImagePNGRepresentation(image);

if (data != nil)

{

NSString *filename = [[self

documentsDirectory] stringByAppendingPathComponent:@"Cool.png"];

NSError *error;

if (![data writeToFile:filename options:NSDataWritingAtomic

error:&error])

{

NSLog(@"Error: %@", error);

}

}

}

NSData writeToFile 方法需要一个 NSError **的参数,如果出现了某种错误,方法会创建一个新的 NSError 对象,并存储错误信息到这个对象中。这就是 out-parameter,通常用来函数返回更多的信息。

如果你在 Xcode 中输入上面代码,你可以看到自动完成提示,(NSError **)参数实际上指定为:(NSError *__autoreleasing *)

这是 ARC 实现 out-parameters 的通用模式,它告诉编译器 writeToFile 方法返回的 NSError 对象是 autorelease 对象。通常你不需要担心和使用__autoreleasing,只有你编写自己的 out-parameter 函数,或者遇到性能问题时,才需要考虑__autoreleasing

  在上面代码中,我们直接定义:
NSError *error;

41/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

这意味着error变量是__strong类型,然后将error传递给 writeToFile方法时,后者接受的却是 __autoreleasing 变量。一个变量不可能同时既是strong 又是 autoreleasing。因此编译器在这里实际上使用了一个临时变量,我们的代码经过编译器处理实际上如下:

{

}

通常这个额外的临时变量没什么大不了,不过如果你确实希望避免编译器为你生成,可以如下编写代码:

{

}

现在 error 变量和 writeToFile 的参数类型就一致了,因此编译器不会进行转换。当然,我个人不会这样使用__autoreleasing,多数情况下,上面代码都是不必要的优化。

如果你需要编写自己的 out-parameter 方法,可以采用下面代码模式:

- (NSString *)fetchKeyAndValue:(__autoreleasing NSNumber **)value

{

}

方法返回一个 NSString 对象,并且在 out-parameter 中返回一个 NSNumber对象。使用示例如下:

NSError *error;

__autoreleasing NSError *temp = error;

BOOL result = ![data writeToFile:filename options:NSDataWritingAtomic

error:&temp];

error = temp;

if (!result)

NSLog(@"Error: %@", error);

__autoreleasing NSError *error;

if (![data writeToFile:filename options:NSDataWritingAtomic

error:&error])

NSLog(@"Error: %@", error);

NSString *theKey;
NSNumber *theValue;

// do whatever you need to do here

*value = theValue;
return theKey;
NSNumber *value;

NSString *key = [self fetchKeyAndValue:&value];

42/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

顺便说下,out-parameter 的默认所有权是 __autoreleasing,因此你实际上可以简化方法定义为:
- (NSString *)fetchKeyAndValue:(NSNumber **)value
{

...

}

反过来,如果你不想为 out-parameter 使用 __autoreleasing,不想把对象放到 autorelease pool,你可以使用 __strong 来声明 out-parameter:
- (NSString *)fetchKeyAndValue:(__strong NSNumber **)value
{

...

}

对于 ARC 来说,它实际上并不关心 out-parameters 到底是 autorelease 还是 strong,编译器总是会做正确的事情。但是如果你想要在非 ARC 环境中使用这个方法,就需要你自己对返回的对象进行手动的 [release]。否则就会导致内存泄漏,因此 out-parameters 返回 retain 对象时必须给出明确的文档!

还有一件事情你需要知道,有些 API 方法会使用自己的 autorelease pool,例如 NSDictionary enumerateKeysAndObjectsUsingBlock:方法首先设置一个autorelease pool,然后再调用你提供的 block。这意味着你在 block 中创建的任何 autoreleased 对象都会被 NSDictionary pool 释放,一般这也正是你想要的效果。但是下面情况例外:
- (void)loopThroughDictionary:(NSDictionary *)d error:(NSError **)error{

[d enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)

{

// do stuff . . .

if (there is some error && error != nil)

{

*error = [NSError errorWithDomain:@"MyError"

code:1 userInfo:nil];

}

}];

}

error 变量是一个 out-parameter,因此也是 autoreleased,由于enumerateKeysAndObjectsUsingBlock:拥有自己的 autorelease pool,你创建的 error 对象会在方法返回之前被释放。要解决这个问题,你需要使用一个临时的 strong 变量来保存 NSError 对象:
- (void)loopThroughDictionary:(NSDictionary *)d error:(NSError **)error{

__block NSError *temp;

43/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

[d enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)

{

userInfo:nil];
// do stuff . . .
if (there is some error)

{

temp = [NSError errorWithDomain:@"MyError" code:1

}

}];

if (error != nil)
*error = temp;

}

autorelease 对象还可能会比你想象中存活更长时间,在 iOS 中,每次 UI事件(点击按钮等)都会清空一次 autorelease pool,但是如果你的事件处理器进行了大量操作,例如循环地创建许多对象,最好是使用你自己
autorelease pool,避免应用面临内存不足:

for (int i = 0; i < 10000; i++)

{

}

ARC 之前的代码中,有时候你会见到循环和 autorelease pool 的一个特殊技巧,X 次循环迭代清空一次 autorelease pool。因为很多人认定NSAutoreleasePool 很慢(实际上并不慢),觉得每次循环都清空 pool 效率低下。其实很没有必要!

现在@autoreleasepool NSAutoreleasePool 要快 6 倍左右,因此直接在循环中使用@autoreleasepool 不会带来任何性能问题。

如果你创建了一个新线程,你需要使用 @autorelease 来封装代码到 autorelease pool 中,虽然语法不同,但原理是一样的。

ARCautorelease对象还有一些更进一步的优化,多数方法都会返回一个 autorelease 对象,但并不是所有这些对象都需要存放在 autorelease pool中,假设你编写了下面代码:
NSString *str = [NSString stringWithFormat:. . .];

stringWithFormat 方法返回一个 autorelease 对象,但 str 是一个 strong类型的本地变量。当 str 超出作用域时,string 对象会被释放。因此这里没有

@autoreleasepool

{

NSString *s = [NSString stringWithFormat:. . .];

}

44/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

必要再将 string 对象放进 autorelease poolARC 通过一些特定的技术手段,能够识别出这一类代码,不会将这些对象进行 autorelease。因
@autoreleasepool 不仅更快,而且还能减少对象的存入。

最后注意 Core Foundation 对象不能 autorelease,autorelease 完全纯属于 Objective-C。有些人通过转换 Core Foundation 对象为 id,再调用autorelease,然后再强制转换回 Core Foundation 对象:
return (CGImageRef)[(id)myImage autorelease];

ARC 中这明显不能工作,因为你不能调用 autorelease。如果你确实要试一试 autorelease 一个 Core Foundation 对象,可以编写自己
CFAutorelease() 函数,并把函数放到一个设置了 -fno-objc-arc 的文件中进行编译。

Cocos2D Box2D

前面讲的是 ARC 与基于 UIKit App,对于 Cocos2D 游戏,ARC 的规则是一样的,但由于当前版本的 Cocos2D 并没有 100%ARC 兼容,因此将 Cocos2D 库整合到你的 ARC 应用时,仍然需要一点点额外工作。

注意:随着时间和 Cocos2D 项目的进展,与 ARC 的兼容肯定会越来越好,因此某些代码可能已经根据 ARC 进行了变化。另外 Cocos2D 2.0beta 1.1beta版本都已经完全兼容 ARC,不过暂时都还不是稳定版。

无论如何,除非你完全重写 Cocos2D,使其全面支持 ARC(这明显是不现实的)。否则你的项目使用 Cocos2D 时,都需要对 Cocos2D 源代码禁止 ARC,只有你自己的代码才能享受 ARC 带来的好处。

Cocos2D 下载及安装请参考:

http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:lesson_1._inst
all_test

整合 Cocos2D 到你的项目有以下几种方式:

1. 使用 Cocos2D 提供的 Xcode project template 来创建你的项目,这是官方推荐使用的方法。不过这样做可能需要你在项目配置中对每个 Cocos2D源文件设置 -fno-objc-arc 编译标志,以禁止 Cocos2D 使用 ARC

2.直接把Cocos2D安装目录下的 cocos2d-ios.xcodeproj文件拖拉到你的 Xcode 工程中,这样会把整个 Cocos2D 工程显示你的工程之下。这样做的好处是你可以直接禁止整个 Cocos2D 项目使用 ARC,而不用对每个源文件进行设置。

3. 直接把Cocos2D的源代码拖拉到你的Xcode工程中,这是最不推荐的方法。45/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

我们使用方法 2,详细步骤如下:

  • 拖拉 cocos2d-ios.xcodeproj 工程文件到你的 Xcode 工程中。

  • Xcode 工程的 Build Phases 设置中添加 Cocos2D 库文件到 Link

    Binary With Libraries 中。

Xcode 工程的 Build Settings 中,设置 Always Search User Paths选项为 YES,然后添加 Cocos2D 源目录到 User Header Search Paths 中,并且设置为 Recursive:

现在你的项目就已经配置好了,Cocos2D 不使用 ARC,你的项目则使用 ARC。但是如果你进行编译的话,会出现 ARC 相关的编译错误。因为你的源文件需要包含 cocos2d.h (cocos2d.h 则包含了所有你需要的头文件)。

46/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

Cocos2D 并没有完全兼容 ARC,因此只有禁用 ARC,才能够成功编译。但你的源文件包含了 cocos2d.h,在 ARC 环境中编译就自然会出现 ARC 不兼容的错误。下面我们需要修改一些 Cocos2D 的头文件和源文件,使我们的项目能够成功在ARC 中编译。

注意这里的代码修改仅供参考,需要根据你使用的 Cocos2D 版本进行调整。

CCDirectorIOS.h:

@interface CCDirectorFast : CCDirectorIOS

{}

这里明显不能使用 NSAutoreleasePool,CCDirectorFast.m 源文件仍然需要使用,所以需要将它移动到 CCDirectorFast.m 中去:

CCDirectorFast.m:

@implementation CCDirectorFast

{

}

CCActionManager.h

typedef struct _hashElement

{

BOOL isRunning;

NSAutoreleasePool *autoreleasePool;

NSAutoreleasePool *autoreleasePool;

struct ccArray *actions;

__unsafe_unretained id target;

NSUInteger actionIndex;

__unsafe_unretained CCAction *currentAction;

BOOL currentActionSalvaged;

BOOL paused;

UT_hash_handle hh;
} tHashElement;

因为结构体在 ARC 中不能再包含 Objective-C 对象,我们需要添
__unsafe_unretained 修饰符,告诉编译器我们知道自己在做什么,不要报错!

ccCArray.h

typedef struct ccArray {
NSUInteger num, max;

__unsafe_unretained id *arr;

} ccArray;

47/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com做完这些,你应该就可以成功编译你的 Cocos2D 项目了。不过假如你还使用

了其它第三方库,可能还需要进行其它 ARC 相关的调整。自己去研究下吧。

静态库 static library

目前并不是所有第三方库都带有 .xcodeproj 文件,能够用来构建一个静态库。你可以把第三方库的源文件添加到你的工程,然后手动设置这些源文件的ARC编译选项 -fno-objc-arc,能用但有点麻烦!

把第三方库构建成独立的静态库,并禁止其使用 ARC,是更加方便合理的选择。

下面我们仍然以 Cocos2D 为例,但是我们从头开始创建一个自己的静态库工程:

Xcode 中选择 New Project,工程类型使用 iOS\Framework &Library\Cocoa Touch Static Library

Product Name 可以直接使用"cocos2d",禁止 Automatic Reference Counting

Xcode 为我们自动生成了一些默认文件,我们首先在 Xcode 中删除 cocos2d源文件目录

然后选择 Xcode 工程中的 Add Files,浏览到你下载解压出来的 Cocos2D安装目录,并选择添加 "cocos2d" 目录,注意确保选中了 Copy items intodestination group's folder,同时 Add to targets 选中"cocos2d"

重复这个操作,把 CocosDenshion external/FontLabel 目录也添加进来,另外再添加 external/Box2d/Box2D目录,注意这里是两个Box2D,其它的Testbed 文件我们并不打算添加进来。

下面需要修改一些编译设置,选中 cocos2d 工程,进入 Build Settings

  • Search Paths\Always Search User Paths 设置为 YES

  • Search Paths\User Header Search Paths 设置为 ./**

    这样设置后,Box2D 才能找到自己需要的头文件

    同时请确保 "Skip Install" 设置为 YES,否则你将不能为 App Store 打包发布文件,因为静态库不应该被安装到 package 中。

    最后,默认的 Xcode 静态库工程配置了一个预编译头文件(Prefix file),但 Cocos2D 并没有使用,之前我们已经把这个预编译头文件删除了,因此需要修改编译选项,告诉编译器不要使用预编译头文件,在 Build Settings 中查找"pch":

48/ 49

iOS 5 ARC 完全指南 GungYi Inc http://www.gungyi.com

直接删掉 Prefix Header 选项就行了。好了,Cmd+B 应该可以编译出自己的 Cocos2D 静态库了!

最后?

ARC 是对 Objective-C 语言的一个重要改进,虽然 iOS 5 中的其它新东西也很 cool,但 ARC 几乎完全改进了我们编写 App 的方式。虽然现在 ARC 仍然处于过渡期,使用的过程中会遇到一些兼容性问题。但根据 Apple 的态度,以及 iOS平台的发展,ARC 在不久的将来肯定会成为主流!

最后再教你一招!如果你维护一个可重用的库,并且不想切换到 ARC(还不想吗?),你可以使用预处理指令在必要时保持与 ARC 兼容:

或者假如你还想支持老的 GCC compiler:

参考资料



#if __has_feature(objc_arc)

// do your ARC thing here

#endif

#if defined(__has_feature) && __has_feature(objc_arc)

// do your ARC thing here

#endif

Transitioning to ARC Release Notes:

https://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-

TransitioningToARC/_index.html

Cocos2D and ARC:

http://www.tinytimgames.com/2011/07/22/cocos2d-and-arc/

iOS 5 By Tutorials》:

http://www.raywenderlich.com/store/ios-5-by-tutorials。本文实际上是

iOS 5 By Tutorials》一书中 ARC 两章的笔记,在此感谢作者!

49/ 49页 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ARCMAP使用手册,目录如下 第一章 欢迎使用 ArcMap 3 可视化信息 4 用地理方式工作 5 显示关系 6 解决问题 7 创建和更新数据 8 展示结果 9 开发制图应用 10 学习ArcMap的技巧 11 第二章 快速入门教程 13 练习 1:浏览数据 14 练习 2:操作地理要素 28 练习 3:操作表(table) 42 练习 4:编辑要素 51 练习 5:操作地图元素 59 第三章 ArcMap 基础 65 制图基础 66 制图与GIS 72 图层、数据框和内容表 73 启动ArcMap 75 打开地图 77 使用内容表 78 数据视图与布局视图 80 移动地图 81 设置书签 79 打开放大镜和全图窗口 86 浏览地理数据 87 在 ArcMap 中使用地图缓存改进地理数据库性能 90 操作地图缓存 91 获得帮助 95 保存地图并退出 ArcMap 98 ArcMap 中的快捷键 显示数据 第四章 显示地图数据 105 创建新地图 106 添加图层 108 添加 coverage、shape 文件和地理数据库 110 从Internet 添加数据 112 从 GIS 服务器添加数据 113 添加TIN 115 添加CAD 图形 116 添加 x,y 坐标数据 118 添加路径事件 119 创建和添加新的要素类 121 关于坐标系 123 指定坐标系 126 如何在地图上参照数据 130 在地图上参照数据 131 修复和更新数据链接 132 第五章 操作图层 135 图层文件描述 136 图层属性功能 137 添加图层 138 改变图层文本 139 图层显示顺序 140 复制图层 141 删除图层 142 图层分组 143 保存图层 146 访问图层属性 147 按特定比例尺显示图层 148 创建透明图层 150 改变图层的数据源 151 改变内容表的外观 153 使用数据框组织图层 154 第六章 要素符号化 157 地图画廊 158 用一种符号绘制所有要素 163 绘制要素来显示其种类(如名字或类型) 165 管理地图类型 168 定量数据制图 171 标准分类方案 172 设置分类 175 用颜色表达数量 178 用分级或比例符号表达数量 180 用点密度表达数量 183 用图表表达数量 185 绘制要素显示多种属性 188 绘制TIN 189 绘制CAD 图层 191 高级符号化操作 193 第七章 操作图形和文本 203 操作图形 204 绘制点、线和圆 205 选择图形 209 移动、旋转和排列图形 210 对齐、分布和分组图形 213 图层合并 215 将图形保存为注记 216 在ArcGIS 中操作文本 218 添加文本 220 操作标注 224 显示标注 229 指定标注文本 233 建立标注表达式 235 标注的优先级与定位 238 将标注转换为注记 241 操作注记 246 显示注记 249 使用文本格式化标签 251 第八章 操作样式和符号 253 样式管理器 254 控制 ArcMap 中的参照样式 255 组织样式内容 256 保存当前样式 258 创建和修改符号与地图元素 259 创建线状符号 261 创建填充符号 264 创建标注符号 268 创建文本符号 271 修改并保存符号与元素 275 操作颜色 277 操作调色板 280 第九章 操作栅格数据 283 添加栅格数据集到地图中 284 使用栅格目录 286 为栅格数据集和栅格目录着色 287 RGB 合成着色器 289 唯一值着色器 290 拉伸着色器 291 分类着色器 292 彩色地图着色器 293 栅格数据分辨率 294 增强栅格数据显示和性能的方法 295 使用金字塔索引快速画图 297 使用效果工具条 298 应用对比度拉伸 300 改变背景值的外观 301 使用地理数据库栅格目录选择环境 302 动态投影栅格数据 304 关于地理参照(Georeferencing) 305 地理参照工具条 307 建立栅格数据的地理参照 308 询数据 十章 操作表 313 表的组成元素 314 打开图层属性表 315 加载现有表格数据到地图 316 安排列 317 控制表的外观 320 记录的定位与浏览 323 记录的排序 325 选择记录 327 导出记录 330 数据汇总 331 添加和删除字段 332 编辑属性 333 进行字段计算 335 关于属性表的连接 337 连接属性表 340 十一章 使用统计图 345 选择统计图类型 346 创建统计图 347 显示统计图 350 修改统计图 351 统计图的静态复制 357 管理统计图 358 保存和加载统计图 359 输出统计图 360 第十二章 创建报表 361 关于报表 362 创建简单报表 366 设置报表的类型和大小 368 操作字段 370 组织报表数据 374 添加报表元素 376 控制报表外观 381 保存和加载报表 384 使用Crystal 报表 386 第十三章 查询地图 389 识别要素 390 显示要素的 Web 页或文档 391 交互式选择要素 393 使用 SQL 表达式搜索选择要素 397 构建SQL 表达式 398 按位置查找要素的方法 402 按位置选择要素 404 高亮显示选中要素 405 显示选中要素的信息 406 导出选中要素 408 按位置连接要素属性 410 深入学习地理处理 413 第十四章 网络分析 415 几何网络 416 打开几何网络 417 网络中要素的符号化 419 添加网络要素 421 网络要素的连接与断开 423 添加网络分析工具条 424 查看网络分析工具条 425 流向 428 显示流向 430 设置流向 432 网络追踪 434 追踪操作 437 地图输出 第十五章 地图排版与打印 453 关于地图模板 456 从模板中启动地图 457 将地图保存为模板 458 设置页面 460 定制数据框 463 使用标尺、导线和格网 466 添加数据框 473 添加与数据框相关的地图元素 477 创建格网和经纬网 489 添加其他地图元素 495 地图元素的对齐与分组 500 打印地图 502 改变布局 507 输出地图 508 定 制 第十六章 定制 ArcMap 513 用户界面的基本元素 514 工具条的显示与隐藏 517 创建自定义工具条 518 修改工具条内容 520 修改上下文菜单 522 修改命令外观 525 创建快捷键 527 保存定制到模板 530 修改定制的缺省保存位置 532 设置工具条选项 533 宏的创建、编辑和运行 534 使用 VBA 创建自定义命令 537 操作UIControls 539 添加自定义命令 540 更新ArcID 模块 541 锁定文档和模板 542 修改 VBA 安全性 544
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值