关于ios:ObjectiveC之内存管理漫谈

34次阅读

共计 15425 个字符,预计需要花费 39 分钟才能阅读完成。

内存治理是程序在运行时分配内存、应用内存,并在程序实现时开释内存的过程。在 Objective- C 中,也被看作是在泛滥数据和代码之间调配无限内存资源的所有权 (Ownership) 的一种形式。

内存治理关怀的是清理或回收不必的内存,以便内存可能再次利用。如果一个对象不再应用,就须要开释对象占用的内存。Objective- C 提供了两种内存治理的办法:手动治理内存计数 (MRR) 和主动援用计数(ARC)。这两种办法都采纳了一种称为“援用计数”的模型来实现,该模型由 Foundation 框架的 NSObject 类和运行时环境(Runtime Environment)独特提供。上面,咱们就先来介绍下什么是援用计数。

1 援用计数

援用计数(Reference Count)是一个简略而无效的治理对象生命周期的形式,个别概念是:当创立一个新的对象时,初始的援用计数为 1。为保障对象的存在,每当创立一个援用到该对象时,通过给对象发送 retain 音讯,为援用计数加 1;当不再须要对象时,通过给对象发送 release 音讯,为援用计数减 1;当对象的援用计数为 0 时,零碎就晓得这个对象不再应用了,通过给对象发送 dealloc 音讯,销毁对象并回收内存。个别在 retain 办法之后,援用计数通常也被称为保留计数(retain count)。


为了更好地论述援用计数的机制,这里援用
开关房间灯的例子来阐明:

假如办公室的照明设备只有一个,进入办公室的人须要照明,来到办公室的人不须要照明。因而,为保障办公室仅有的照明设备失去很好的治理,只有办公室还有人,就会须要照明,灯就得开着,而当办公室没人的时候,就须要关灯。为了判断办公室是否还有人,咱们导入计数性能来计算“须要照明的人数”:

  • 第一个进入办公室的人,须要照明,此时,“须要照明的人数”为 1,计数值从 0 变为 1,须要开灯。
  • 第二个进入办公室的人,也须要照明,此时,“须要照明的人数”为 2,计数值从 1 变为 2。
  • 之后,每一个进入办公室的人都须要照明,此时“须要照明的人数”顺次减少,计数值也顺次加 1。
  • 当第一个人来到办公室时,不再须要照明,此时“须要照明的人数”减 1,计数值也减 1。
  • 之后,只有有人来到办公室,就不再须要照明,此时“须要照明的人数”顺次缩小,计数值顺次减 1。
  • 当最初一个人来到办公室时,不再有人须要照明,“须要照明的人数”为 0,计数值减至 0,须要关灯。

在 Objective- C 中,对象就相当于办公室的照明设备,而对象的应用环境就相当于进入办公室下班的人。其对应关系能够用以下表格来示意:
进入办公室下班的人对照明设备所做的动作

对象的应用环境对 Objective- C 对象所做的动作

进入办公室下班的人对照明设备所做的动作 对象的应用环境对 Objective- C 对象所做的动作
开灯 生成对象
须要照明 持有对象
不再须要照明 开释对象
关灯 销毁对象

2 手动治理内存 MRR

手动治理内存,即 MRR(manual retain-release),是基于援用计数来实现的,通过本人跟踪对象来明确治理内存。它与 ARC 之间的惟一区别是:在 MRR 中,对象的保留和开释都是由咱们手动解决,而在 ARC 中是主动解决的。

2.1 MRR 内存治理的根本准则

为了不便了解,咱们先通过一个例子看下 MRR 中的内存治理是如何工作的,之后会有总结。

首先关上Xcode,创立一个新的我的项目(File\New\Project…), 在这里咱们将项目名称写为MemoryManagementDemo。为了确保咱们的代码是在 MRR 环境下,在进行任何操作之前,咱们须要先查看以后我的项目是否启用了 ARC,如果是,将它敞开,如下图所示:

咱们来总结一下手动治理内存的规定:

  • allocnewcopymutableCopy结尾的办法创立的对象,咱们领有该对象,应用实现后须要调用 releaseautorelease开释。
  • init 办法中为了获取对象的所有权,或者在某些状况下防止对象被移除,能够应用 retain 保留对象。在应用完对象后,须要应用 release 进行开释。
  • 对应用了 retaincopymutableCopyallocnew办法的任何对象,以及具备 retaincopy个性的属性进行开释,须要重写 dealloc 办法,使得在对象被开释的时候可能开释这些实例变量。
  • 给对象发送 release 音讯并不一定立刻销毁这个对象,只有当对象的援用计数减至 0 时,对象才会被销毁,而后零碎会发送 dealloc 音讯给这个对象用于开释它的内存。
  • 如果在办法中不再须要用到某个对象,但须要将其返回,能够给该对象发送 autorelease 音讯用以标记提早开释,对象的援用计数会在以后主动开释池的开端减 1。
  • 当应用程序终止时,内存中的所有对象都会被开释,不管它们是否在主动开释池中。
  • 当不再须要一个对象时,必须放弃所领有的该对象的所有权。
  • 不能放弃一个你所不领有的对象的所有权。

2.2 主动开释池(autorelease pool)

下面有说到主动开释,在这里咱们简略介绍下主动开释池。

主动开释池创立的目标就是心愿能够帮忙追踪须要提早一些工夫开释的对象。
通过给对象发送 autorelease 音讯,就能够将一个对象增加到由主动开释池保护的对象列表中:

[object autorelease];

程序中应用来自 Foundation、UIKit、AppKit 框架的类时,首先须要创立一个主动开释池,这样来自这些框架的类才会创立并返回主动开释的对象,须要在程序中应用 @autoreleasepool 指令(个别在我的项目中的 main.m 文件中就会看到该语句):

@autoreleasepool {statements}

当执行到 autorelease 块的开端时,零碎就会对池中的每个对象发送 release 音讯,这将影响到所有发送过 autorelease 音讯并被增加到主动开释池中的对象,当这些对象的援用计数减至 0 时,会发送出 dealloc 音讯,并且它们的内存将会被开释。

须要留神的是,主动开释池并不蕴含理论的对象,只是蕴含对象的援用,对象将在主动开释池清理的时候被开释。在 ARC 中,主动开释池次要用于升高内存峰值,只是咱们不再须要手动增加 autorelease 的代码了。

2.3 应用拜访器办法简化内存治理

手动治理内存时,在代码中应用 retainrelease来保留或开释对象难免会出错,为此,咱们能够应用拜访器办法来缩小内存治理中的问题。

通常所说的拜访器(accessor)办法指的是设值办法(setter)和取值办法(getter),又统称为存取方法。设值办法即设置实例变量值的办法,次要目标是将办法参数设为对应的实例变量的值,个别不会返回任何值。取值办法即检索实例变量值的办法,次要目标是获取存储在对象中的实例变量的值,并通过程序返回发送进来,所以取值办法必须返回实例变量的值作为 return 的参数。

上面咱们持续延用之前的例子,在 TableViewController.h 文件中为两个实例变量增加设值办法和取值办法:

@interface TableViewController : UITableViewController
{
    NSArray *_titles;
    NSString *_lastTitleSelected;
}

- (void)setTitles:(NSArray *)titles;
- (NSArray *)titles;
- (void)setLastTitleSelected:(NSString *)lastTitleSelected;
- (NSString *)lastTitleSelected;

@end

而后在 TableViewController.m 文件的底部增加实现办法:

#pragma mark - Accessor method

- (void)setTitles:(NSArray *)titles
{[titles retain];
    [_titles release];
    _titles = titles;
}

- (NSArray *)titles
{return _titles;}

- (void)setLastTitleSelected:(NSString *)lastTitleSelected
{[lastTitleSelected retain];
    [_lastTitleSelected release];
    _lastTitleSelected = lastTitleSelected;
}

- (NSString *)lastTitleSelected
{return _lastTitleSelected;}

下面的 getter 办法容易了解,它们只返回了实例变量。而在 setter 办法中,咱们首先应用 retain 保留新传入的参数变量以减少援用计数,而后应用 release 开释掉旧的实例变量以缩小援用计数,最初将实例变量的值设置为传入的参数变量。这样,只有设置对象,就能保障在实例变量中存储的对象有正确的援用计数。另外,应用这样的程序设置实例变量,能够避免将实例变量设置为同一对象的状况。还有,如果你仔细观察下面的设值办法,你就会明确咱们在最开始命名实例变量的时候要应用下划线的起因,最次要的是为了防止由实例变量名称和参数变量名称雷同而引起的抵触。

接下来咱们就应用这些存取方法来批改代码中的实例变量。

首先咱们应用设值办法来批改 viewDidLoad 中的_titles

- (void)viewDidLoad
{[super viewDidLoad];
    
    [self setTitles:[[[NSArray alloc] initWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"10", nil] autorelease]];
}

察看认真的话,你会发现除了应用设值办法外,在开端咱们还调用了 autorelease。还记得在 setter 办法中咱们应用retain 将传入的参数变量援用计数加 1 吗?这里咱们应用了 initWithObjects: 创立数组对象,会导致变量最终的援用计数为 2,因而必须应用主动开释来缩小援用计数。当然,咱们也能够应用 arrayWithObjects: 办法创立数组对象,这样就不须要再调用 autorelease 了。

另外,为了不便,Objective- C 语言容许咱们应用点运算符 . 代替方括号 [] 来设置或获取实例变量的值,即:

self.titles = xxx;

相当于:

[self setTitles:xxx];

点运算符 . 通常用于属性,但不限于属性。

上面咱们应用点运算符 . 再次批改下面的办法:

- (void)viewDidLoad
{[super viewDidLoad];
    
    self.titles = [[[NSArray alloc] initWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"10", nil] autorelease];
}

接下来在 tableView:didSelectRowAtIndexPath 办法中批改_lastTitleSelected

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    ...
    
    // 创立警报视图以显示弹出的音讯
    ...
    [self presentViewController:alert animated:YES completion:nil];
    
    // 设置实例变量(删除最初两行代码,用上面的代码代替)
    self.lastTitleSelected = title;
}

咱们将实例变量的内存治理的代码都写在了 setter 办法中,所以在下面的代码中设置实例变量就简略了很多。

另外须要阐明的一点是,文档中有提到:不要在初始化办法和 dealloc 办法中应用拜访器办法:

The only places you shouldn’t use accessor methods to set an instance variable are in initializer methods and dealloc.

如果要在 init 办法中初始化一个对象,个别应用上面的模式:

- (instancetype)init
{self = [super init];
    if (self) {instance = ...}
    
    return self;
}

dealloc 办法中也要应用实例变量:

- (void)dealloc
{[instance release];
    [super dealloc];
}

之所以说不要在 initdealloc办法中应用拜访器办法,次要是因为面向对象的继承、多态个性与拜访器办法可能造成的副作用联结导致的。继承和多态导致在父类的实现中调用拜访器办法时可能会调用到子类重写的存取方法,而此时子类局部并未齐全初始化或曾经销毁,导致凌乱,从而呈现一系列的逻辑问题甚至解体。但这种说法也不是相对的,只管存在危险但并不代表百分之百的解体或谬误,如果你在程序中有这样写,并且明确地晓得它不会产生任何问题,那么就能够应用它来简化你的代码。比方,在这里咱们批改 dealloc 中的代码:

- (void)dealloc
{
    self.titles = nil;
    self.lastTitleSelected = nil;
    
    [super dealloc];
}

下面删除了之前手动调用 release 及将其设置为 nil 的两行代码,而后应用 setter 办法代替。当初咱们将 nil 作为设值办法里的参数变量传递,即:

[nil retain];
[_titles release];
_titles = nil;

因为代码中 [nil retain] 不会执行任何操作,因而咱们在 dealloc 办法中应用 self.titles = nil 不会产生任何问题。不过为了平安起见,在不分明是否会产生问题的状况下,咱们还是倡议恪守文档中的阐明。

2.4 应用属性

除了手动编写存取方法,Objective- C 还提供了属性(property)不便咱们疾速地为实例变量创立拜访器办法,并可选地实现它们。

属性的申明个别在接口局部,以 @property 关键字结尾,前面能够选择性的定义属性的个性,而后以属性的类型和名称(个别状况下咱们应用与实例变量雷同的名称,但并不是必须的)结尾。上面是几种无效的属性申明:

@property int age;
@property (copy) NSString *name;
@property (nonatomic, strong) NSArray *array;

须要留神的是,在申明属性时,属性的名称后面不要以 newalloccopy 或者 init 这些词语结尾。

申明属性的操作就相当于申明 setter 和 getter 办法,以下面的 age 为例:

@property int age;

就相当于:

- (void)setAge:(int)age;
- (int)age;

个别零碎默认的设值办法名称是以 set 结尾(setPropertyName),默认的取值办法是以属性名称命名(propertyName)。如果想要更改成自定义的名称能够应用上面的办法:

  • 用 setter = setterName 来指定 setter 办法的名称,如:
@property (setter = setterName) int age;
  • 用 getter = getterName 来指定 getter 办法的名称,如:
@property (getter = getterName) int age;

个别罕用于 BOOL 类型,getter 办法通常以 is 结尾,比方标识一个视图是否暗藏的 hidden 属性,其 getter 办法应该称为isHidden。能够这样申明:

@property (nonatomic,getter = isHidden) BOOL hidden;

接下来咱们在类的实现局部应用 @synthesize 通知编译器主动为属性实现一个设值办法和取值办法,即:

@implementation Class

@synthesize age;

@end

其中 @synthesize age 默认指定的实例变量名称与属性雷同,即:

@synthesize age = age;

Xcode 4.4 当前,编译器引入了属性自动合成(property autosynthesis),也就是说编译器会为每一个 @property 增加 @synthesize,咱们不须要再显式地应用@synthesize 指令了。但须要留神的是,自动合成默认生成的实例变量名称以 _ 为前缀,加上属性名,即:

@synthesize age = _age;

如果不喜爱默认的实例变量名称,或者咱们心愿应用更有语义的名称,就须要通过 @synthesize 来指定等号前面的名称作为咱们心愿的实例变量名:

@synthesize age = currentAge;

还须要阐明的是,通常编译器会自动合成一个实例变量和至多一个拜访器办法,如果咱们为带有 readwrite 关键字的属性同时手动实现了 setter 和 getter 办法,或为带有 readonly 关键字的属性实现了 getter 办法,那么编译器会假设咱们正在对属性的实现进行管制,并且不会自动合成实例变量。在这种状况下,咱们就须要手动指定一个实例变量:

@synthesize property = _property;

另外须要留神的是,个别状况下应用 @property 会在编译期间自动合成存取方法,但有些存取方法是在运行时动态创建的,这时申明和应用属性时会因为短少办法而在编译期间收回正告,这时候咱们能够应用 @dynamic 语句来克制正告:

@implementation Class
@dynamic age;
@end

上面咱们就应用属性替换咱们代码中的存取方法:

首先咱们在 TableViewController.h 文件中删除实例变量和存取方法的申明,应用属性来代替:

@interface TableViewController : UITableViewController

@property (nonatomic, retain) NSArray *titles;
@property (nonatomic, retain) NSString *lastTitleSelected;

@end

而后切换到 TableViewController.m 文件,删除之前写的 setter 和 getter 办法,并在实现局部的最顶部增加@synthesize

@implementation TableViewController

@synthesize titles = _titles;
@synthesize lastTitleSelected = _lastTitleSelected;

当然这里申明的实例变量与默认的统一,因而下面的 @synthesize 也能够省略不写。

当初能够运行代码试下,你会发现模拟器跟以前一样失常显示。然而显然这种让编译器主动生成存取方法的做法比咱们手动编写存取方法要简略很多,同时也更高效,并且在多核设施上能够应用多线程运行。

2.5 属性的个性

属性的个性是应用一些非凡的关键字来通知编译器如何合成相干的拜访器办法。在咱们的代码中,申明属性时应用了 nonatomicretain。在此之前咱们讲到属性的申明时,在示例中也应用到了copy,但属性的关键字远不止这几个,个别分为线程相干、拜访器相干以及内存相干这三类。上面是具体介绍。

2.5.1 线程相干:atomic、nonatomic

atomic(默认)

  • 默认个性,如果没有应用任何关键字指定属性的个性,默认状况下 Objective- C 属性的个性是atomic
  • atomic示意原子属性,应用该属性是为了通知零碎应用互斥(mutex)锁定爱护属性的存取方法,如果以后过程进行到一半,其余线程来拜访以后线程,能够保障先执行完以后线程。也就是说,即便从不同的线程同时调用拜访器办法,也可能保障有一个值总能被 getter 办法齐全检索到或者被 setter 办法齐全设置。比方,在多线程的程序中,如果有多个线程指向雷同的实例变量,一个线程能够读取,另一个线程能够写入。当它们在同一时间点击时,读取的线程将被保障可能获取到一个无效的值,可能是更改前的值,也可能是更改后的值。
  • 原子属性与对象的线程安全性是不同的,并且应用原子属性并不能保障线程平安。如果有多个线程同时拜访同一个实例变量,其中一个线程调用release,就会造成程序解体。
  • 应用 atomic 让编译器生成的互斥锁定代码会很消耗资源,使程序变慢影响效率,所以个别很少应用。
  • atomic属性合成的拜访器办法是公有的,因而不能与本人实现的拜访器办法相结合。如果咱们尝试为 atomic 属性提供自定义的 setter 或 getter 办法,会收到编译器的正告:


nonatomic

  • nonatomicatomic 相同,示意非原子属性。应用该属性是为了通知零碎不要应用互斥锁定爱护属性的存取方法。当有多个线程同时拜访同一个属性时将会导致无奈预计的后果。在这里不能保障调用拜访器办法时会返回一个无效的值。如果尝试在写入的两头读取时,咱们可能会失去一个有效的值。
  • atomic 比起来,nonatomic效率要更高一些,如果要屡次拜访一个属性时,应用 nonatomic 会更高效。
  • 个别在单线程和明确晓得只有一个线程拜访的状况下宽泛应用。
  • 应用 nonatomic 能够将自动合成的 setter 或 getter 办法与本人手动实现的 getter 或 setter 办法相结合。因而,对于应用 atomic 实现 setter 或 getter 时呈现的正告,能够通过设置属性的个性为 nonatomic 来解除:

2.5.2 拜访器相干:readonly、readwrite

readonly

  • readonly示意只读属性,对包含本身在内的所有类都是只读的。
  • 合成的拜访器只有 getter 办法没有 setter 办法。

readwrite(默认)

  • 默认个性,个别不须要显式申明。
  • readwrite示意读写属性,即属性容许被本身或其余类读写,与 readonly 相同。
  • 合成的拜访器同时领有 setter 办法和 getter 办法。

如果心愿一个属性只容许本身读写,而对其余类都是只读的,能够在 .h 文件的接口局部中将属性的个性申明为 readonly,而后在.m 文件的公有接口局部再从新将属性个性申明为 readwrite 即可。

2.5.3 内存相干:retain、assign、strong、weak、copy、unsafe_unretained

retain

  • retain示意属性的保留操作,用于获取对象的所有权,会减少传入对象的援用计数。
  • 在属性中应用 retain 关键字能够批示编译器在设置实例变量之前保留传入的变量,在默认的设值办法中会是:
   if (_property != newValue) {[_property release];
       _property = [newValue retain];
   }
  • 次要在手动内存治理中应用,在 ARC 下,个别应用 strong 代替。

assign(默认)

  • 默认个性。个别像 intfloatdoubleNSIntegerCGFloatBOOL等值类型的属性默认应用assign
  • assign用于属性的赋值操作,不存在所有权关系。在默认的设值办法中会是:
   _property = newValue;

copy

  • copy用于创建对象的正本,并且对该正本对象领有所有权,而非原对象自身。
  • 在属性中应用 copy 关键字能够批示编译器在设置实例变量之前创立传入变量的正本,在默认的设值办法中会是:
   if (_property != newValue) {[_property release];
       _property = [newValue copy];
   }
  • copy属性对其创立的正本对象隐式强援用。同时也意味着该属性将应用strong,因为它必须放弃其创立的正本对象。
  • copy 属性设置的任何对象必须恪守 NSCopying 协定。
  • 如果须要间接设置 copy 属性的实例变量,例如在初始化办法中,要记得设置原始对象的正本。

strong(默认)

  • 默认个性。个别 Objective- C 对象的属性默认是strong
  • strong是在引入 ARC 的时候引入的关键字,相当于 MRR 下的retain
  • 应用 strong 申明强援用,示意实例变量对传入的对象领有所有权,只有持有该属性中对象的援用,该对象就不会被开释。如果有两个强援用的对象互相指向对方,就会造成强援用循环。
  • 须要辨别的是,在 Objective- C 中,对象属性默认是strong,而对象变量默认是__strong

weak

  • weak也是 ARC 下的属性关键字,但它是从 iOS 5 引入,因而在 iOS 5 之前不可用。
  • 应用 weak 申明弱援用,与 strong 相同,它示意实例变量对传入的对象没有所有权,并且在 setter 办法中也不会对传入对象减少援用计数。当对象被开释后,实例变量会主动设置为 nil。
  • 对于变量来说,咱们能够应用 __weak 将变量申明为弱指针变量,并且在理论利用中,咱们也通过应用 __weak 将强援用替换为弱援用,以此来解决强援用循环的问题:
 NSObject * __weak weakVariable;
  • 个别状况下,delegate 和 outlet 用 weak 来申明。

unsafe_unretained

  • unsafe_unretained示意不平安的援用,是 iOS 5 之前代替 weak 的关键字。
  • weak 不同的是,应用 unsafe_unretained 申明的属性,当对象被开释后,实例变量不会主动设置为 nil。这意味着咱们将留下一个悬挂指针,指向原先被开释的对象所占用的内存,会导致程序解体,因而它被称为是“不平安的”。

2.6 内存治理要防止的问题

内存治理不正确导致的次要问题有两种:

  • 开释或重写仍在应用的内存导致内存损坏:通常会导致应用程序解体,甚至导致用户数据受到改写或损坏。
  • 没有开释不再应用的内存导致内存透露:会导致应用程序对内存的应用一直减少,从而导致系统性能降落或应用程序被终止。

对于下面第一种问题,咱们能够应用 NSZombieEnabled 调试工具来查找适度开释的对象。对于第二种问题,能够应用 Instruments 跟踪援用计数事件并查找内存透露。

如果想在编译时辨认出代码中的问题,能够应用 Xcode 中内置的动态剖析性能。这将使 XCode 运行咱们的代码,并查找能够自动检测到的任何谬误,以正告咱们有任何潜在的问题。上面咱们就对之前的我的项目应用该性能来检测下是否存在问题:

关上我的项目,在顶部的菜单栏抉择Product\Analyze

下面的音讯通知咱们检测到内存透露,实例变量 _window 须要被开释,短少 dealloc 办法,所以咱们在 AppDelegate.m 的实现局部增加 dealloc 办法,并对实例变量 _window 调用 release 办法:

- (void)dealloc
{[_window release];
    _window = nil;
    
    [super dealloc];
}

再次点击菜单栏中的Product\Analyze,你会看到之前的标记隐没了,同时也没有检测到其余新问题。

3 主动援用计数 ARC

主动援用计数,即 ARC(Automatic Reference Counting),是 Xcode 4.2 版本的一个新个性,应用了与手动治理雷同的援用计数零碎,不同的是,零碎在编译时会帮咱们插入适合的内存治理办法,保留和开释都是主动解决的,从而防止了手动援用计数的一些潜在陷阱。个别在新我的项目中被举荐应用。

3.1 ARC 下内存治理的规定

ARC 的规定很简略,咱们不须要再手动保留和开释对象,须要做的只是治理指向对象的指针。只有有指针指向该对象,该对象将保留在内存中;当指针指向其余对象,或不存在时,该对象将被主动开释。

上面咱们列出在 ARC 下内存治理的规定:

  • 不能显式调用 dealloc,或retainreleaseretainCountautorelease 等,甚至也不能应用 @selector(retain)@selector(release) 等。
  • 如果须要治理除开释实例变量之外的资源,则能够实现 dealloc 办法,并且自定义的 dealloc 办法不须要调用[super dealloc]
  • 拜访器办法的名称不能以 new 结尾,这反过来也意味着不能申明一个以 new 结尾的属性,除非咱们指定了一个不同的 getter:
// 谬误:@property NSString * newTitle;

// 正确:@property(getter = theNewTitle)NSString * newTitle;

3.2 MRR 转化为 ARC

在下面的 demo 中咱们都是用 MRR 来进行内存治理的,当初咱们须要把它转化成 ARC,最容易想到的办法是手动转换,须要把所有调用到 retainrelease 等办法的代码都去掉,但这样做会很麻烦。侥幸的是,Xcode 提供了一个 ARC 主动转换工具,能够帮忙咱们不便地将源码转为 ARC,更灵便的是,它岂但能够将我的项目中的所有文件转换为 ARC,还能够选择性地对指定的文件进行转换,对于一些不想转换的文件能够禁用 ARC。

上面咱们就应用这种主动转换工具转换咱们的代码:

首先,ARC 是 LLVM3.0 编译器的个性,因而咱们先来确认下以后的编译器是否合乎。选中文件中的我的项目,在 Build Settings 搜寻框中输出“compiler”,而后在 Build Options 中查看第一项 Compiler for C/C++/Objective-C 对应的编译器版本:

在转换前咱们先点击 Xcode 菜单栏中的 Product\Build 以确保以后代码没有问题。

接下来从 Xcode 的菜单栏中抉择 Edit\Convert\To Objective-C ARC…

而后在弹出的窗口中点击第一个图标下的小三角能够开展所有文件,在这里,为了比照手动转换,咱们勾销选中 AppDelegate.m 文件,只选中其余两个默认勾选的文件进行转换,而后点击 check

持续在弹出的窗口点击 Next

你会看到正在生成转化:

转换实现后将会显示所有文件的预览。左侧窗格显示已更改的文件,右侧窗格显示原文件。这里显示的是 TableViewController.h 文件,你会看到在属性申明中应用 strong 代替了 retain

接下来咱们切换到 TableViewController.m 文件:

这里一共有两处更改。首先是在 viewDidLoad 办法中,初始化 titles 时删除了 autorelease 的调用。而后删除了 dealloc 办法及内容。

确认后,持续点击 Save 保留更改,就能够在咱们的我的项目中看到之前预览文件的更改,转换实现。

再次编译程序,点击Product\Build,显示编译胜利。

还有一点须要晓得的是,在同一个我的项目中将 ARC 代码与非 ARC 代码相结合是可行的。上面咱们关上 AppDelegate.m 文件,会看到该文件仍然存在 dealloc 办法,并且能够失常运行,这是因为咱们勾销勾选该文件时,转换工具曾经禁用这两个源文件的 ARC,咱们能够在 TARGETSBuild Phases中看到:

AppDelegate.m文件前面被加上了 -fno-objc-arc 的编译标记,示意该文件将不应用 ARC 规定进行编译。相同地,如果想对特定的文件启用 ARC,能够为其增加 -fobjc-arc 标记。在这里,咱们双击 AppDelegate.m 文件前面的的标记,将其更改为 -fobjc-arc 以对该文件启用 ARC:

再次点击 Product\Build 编译程序,会看到谬误提醒,上面咱们就手动更改代码来修复这些谬误:

首先关上 AppDelegate.m 文件,看到谬误次要产生在 dealloc 办法中:

从错误信息中咱们不难看出,次要是因为在 ARC 下调用 releasedealloc导致的。咱们删除整个 dealloc 办法,谬误隐没,再次点击 Product\Build 编译程序,编译胜利。此时,运行程序,会发现一切正常显示。

至此,咱们的转换工作曾经全副实现,我的项目中的所有文件都应用了 ARC。如果对其中的代码还有问题,能够下载 MemoryManagementDemo 查看。

4 Core Foundation 对象的内存治理

4.1 Core Foundation

Core Foundation 是基于 Objective- C 的 Foundation 框架,然而以 C 语言实现。对于大多数应用程序,咱们并不需要应用 Core Foundation,个别从 Objective- C 中就能够实现任何咱们想要的操作。然而,对于一些底层的 API,比方 Core Graphics 和 Core Text 等,就须要咱们对 Core Foundation 有所理解。

个别底层的 Core Foundation 对象大多以 CreateWith 结尾的函数来创立,其内存治理只须要连续手工援用计数的方法即可。对于援用计数的批改,须要应用 CFRetainCFRelease函数,其性能与 Objective- C 对象的 retainrelease办法相似。

    // 创立一个 CFStringRef 对象
    CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault, "string", kCFStringEncodingUTF8);
    
    // 保留该对象,援用计数加 1
    CFRetain(cfString);
    
    // 开释该对象,援用计数减 1
    CFRelease(cfString);

4.2 收费桥接(toll-free bridged)

Core Foundation 框架和 Foundation 框架中有许多数据类型能够调换应用,比方咱们能够应用 NSString 对象将其用作 CFStringRef,也能够应用CFStringRef 对象将其用作NSString。这种能够调换应用的数据类型也被称为收费桥接数据类型。这意味着咱们能够应用雷同的数据结构作为 Core Foundation 函数调用的参数或者 Objective- C 音讯调用的接收者。然而,并不是所有的数据类型都是收费桥接的,具体列表能够参考 Toll-Free Bridged Types。

在收费桥接中,与内存治理相干的一个重要问题就是转换过程中对象的所有权问题。比方在 ARC 下,咱们须要将一个 Core Foundation 对象转换成一个 Objective- C 对象,这时候就须要通知编译器如何治理对象的所有权。于是咱们引入 bridge 相干的关键字来阐明对象的所有权语义:

4.2.1 __bridge

应用 __bridge 能够在 Objective- C 对象和 Core Foundation 对象之间互相转换,此转换只做类型转换,不转移对象的所有权。

  • 应用 __bridge将 Objective- C 对象转换为 Core Foundation 对象,应用实现后由 ARC 负责开释对象:
    // 创立一个 NSString 对象
   NSString *nsString = @"string";
   
   // 将 NSString 对象转换为 CFStringRef 对象
   CFStringRef cfString = (__bridge CFStringRef)nsString;
  • 应用 __bridge将 Core Foundation 对象转换为 Objective- C 对象,实现后须要调用 CFRelease 开释对象:
    // 创立一个 CFStringRef 对象
   CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault, "string", kCFStringEncodingUTF8);
   
   // 将 CFStringRef 对象转换为 NSString 对象
   NSString *nsString = (__bridge NSString*)cfString;
   
   // 开释 CFStringRef 对象
   CFRelease(cfString);
4.2.2 __bridge_retained

应用 __bridge_retainedCFBridgingRetain将 Objective- C 对象转换为 Core Foundation 对象,此转换会将 Objective- C 对象的所有权转移给 Core Foundation 对象,应用实现后须要调用 CFRelease 开释对象所有权。

    // 创立一个 NSString 对象
    NSString *nsString = @"string";
    
    // 将 NSString 对象转换为 CFStringRef 对象
    CFStringRef cfString = (__bridge_retained CFStringRef)nsString;
    
    // 开释 CFStringRef 对象
    CFRelease(cfString);
4.2.3 __bridge_transfer

应用 __bridge_transferCFBridgingRelease将 Core Foundation 对象转换为 Objective- C 对象,此转换会将 Core Foundation 对象的所有权转移给 ARC,应用实现后由 ARC 负责开释对象所有权。

    // 创立一个 CFStringRef 对象
    CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault, "string", kCFStringEncodingUTF8);
    
    // 将 CFStringRef 对象转换为 NSString 对象
    NSString *nsString = (__bridge_transfer NSString*)cfString;

5 参考资料

Advanced Memory Management Programming Guide

Encapsulating Data

Transitioning to ARC Release Notes

Obj-C Memory Management

Memory Management Tutorial for iOS

Properties Tutorial for iOS

Beginning ARC in iOS 5 Tutorial

iOS 核心技术之:内存治理之二手动内存治理

了解 iOS 的内存治理

正文完
 0