在OC这种面向对象的语言中,内存管事是个重要概念。要想用一门语言写出内存使用效率高而且又没有bug的代码,就得掌握其内存管理模型的种种细节。
OC引用给自动引用计数(Automatic Reference Counting,ARC)之后,几乎吧所有内存管理事宜都交由编译器来决定。
29、引用计数
OC通过引用计数来管理内存,每个对象都已个可以递增或递减的计数器。如果想要某个对象继续存活,就递增其引用计数,用完之后,递减计数。计数变为0,表示没人关注此对象,可以销毁。
iOS不支持垃圾收集(garbage collector)。
引用计数工作原理
引用计数架构下,对象有个计数器,用以表示当前有多少个事物想令此对象继续存活下去。在OC中叫引用计数(retain Count),或者引用计数(reference count)。
NSObject声明三个方法用于操作计数器:
- Retain 递增引用计数
- release 递减引用计数
- autorelease 待稍后清理自动释放池(autorelease pool)时,再递减引用计数。
查看引用计数的方法retainCount
,不过不太有用。
对象创建出来后,引用计数至少为1,当引用计数归零时,对象就回收了(deallocated),系统会将其占用的内存标记为可重用。此时,所有指向该对象的引用都变得无效了。
应用周期在生命期中会创建很多对象,这些对象互相关联。相互关联的对象构成一张对象图(object graph)。对象如果持有指向其他对象的强引用(strong reference),那么前者就拥有后者。对象如果想令其所引用的那些对象继续存活,就可将其保留,用完之后,再释放。
ARC 切换 MARC
Project -> Target -> build Settin】-> 输入“AutoMatic” ->Object-C Automatic Reference Count 改为NO
因过早释放对象而导致的bug很难调试,因为对象所占的内存在解除分配(deallocated)之后,只是放回可用内存池(avaiable pool)。如果再次使用还没有覆写对象内存,那么该对象依然有效,程序不会崩溃,但是如果该对象所占内存被回收,再次使用就会崩溃。
为避免不经意间使用无效对象,一般用完release之后,都会清空指针。保证不会出现可能指向无效对象的指针,这种指针通常称为悬挂指针(dangling pointer)。可以再释放对象之后,将对象置为nil
NSNumber *number = [[NSNumber alloc] initWithInt:2];
NSMutableArray *array = [NSMutableArray array];
[array addObject:number];
[number release];
number = nil;
属性存取方法中的内存管理
不光数组,其他对象也可以保留别的对象,一般通过访问属性来实现。而访问属性时,会用到相关实例变量的获取方法及设置方法。
若属性为strong关系,则设置的属性值会保留。
- (void)setFirstName:(NSString *)firstName {
[firstName retain];
[_firstName release];
_firstName = firstName;
}
此方法将保留新值并释放旧值,然后更新实例变量,令其指向新值。顺序很重要,加入还未保留新值就把旧值释放了,而且这两个值又指向同一个对象,那么先执行的release操作就可能导致系统将此对象永久回收。而后续的retain操作则无法领这个已经彻底回收的对象复生,于是实例变量就成了悬挂指针。
自动释放池
调用release会立刻递减对象的引用计数(而且有可能令系统回收此对象),有时候可以不调用它,改为调用autorelease,此方法会在稍后递减计数,通常是在下一次事件循环(event loop)时递减,也可能执行得更早些。
此特性很有用,尤其是在方法中返回对象时更应该用它。这种情况下,我们并不总是想令方法调用者手动保留其值。
- (NSString *)personInfo {
// alloc 引用计数多1
NSString *string = [[NSString alloc] initWithFormat:@"%@",_firstName];
return [string autorelease];
}
ZYDPersonModel *person = [[ZYDPersonModel alloc] initWithFirstName:@"Json"];
NSString *string = [person personInfo];
NSLog(@"%@",string);
使用autorelease来释放对象,string将与稍后自动释放,多出来的一次保留操作就会被抵消,无需再执行内存管理操作。而自动释放池中的释放操作要等下一次事件循环时才会执行,如果不需要保留,这里则不用其他操作。如果需要持有此对象,就需要宝就,并于稍后释放。
personInfo = [[person personInfo] retain];
...
[personInfo release];
这里不能在方法personInfo
内部使用release,否则还没等方法返回,系统就把对象回收了。
保留环
保留环(retain cycle),呈环状互相引用多个对象。这将导致内存泄漏,因为循环中的对象其引用计数不会将为0。
在垃圾回收环境中,这种情况会被认定为孤岛(island of isolation),垃圾回收机制会把循环引用的对象全部回收。
OC通常采用弱引用解决此问题,或者从外界命令循环中的某个对象不再保留另外一个对象。打破保留环,从而避免内存泄漏。
- 引用计数机制通过可以递增递减的计数器来管理内存。对象创建好后,其引用计数至少为1。若引用计数为正,则对象继续存活。当引用计数将为0,对象就被回收。
- 在对象生命周期中,其余兑现刚通过引用来保留或释放此对象,保留与释放操作分别会递增及递减引用计数。
30、以ARC简化引用计数
静态分析器
是ARC时,引用计数实际上还是要执行的,只不过保留与释放操作由ARC自动添加。
ARC的功能都是基于核心的内存管理语义而构建的,这套标准语义贯穿整个OC语言。
ARC会自动执行retain
、release
、autorelease
等操作,所以直接在ARC下调用内存管理方法是非法的:retain
、release
、autorelease
、dealloc
。直接调用会报编译错误。
ARC调用这些方法,并不通过普通的OC消息派发机制,而是直接调用其底层的C语言版本。这样性能更好,因为保留及释放需要频繁执行,所以直接调用底层的函数能节省很多CPU周期。
使用ARC时必须遵循的方法命名规则
将内存管理语义在方法名中表示出来是OC的管理,而ARC将之确立为硬性规定。这些规则简单地体现在方法名上,若方法名以下列词语开头,则返回对象过调用者所有:
- alloc
- new
- copy
- mutableCopy
归调用者所有的意思:调用上述四种方法的那段代码要负责释放方法所返回的对象。也就是说,这些对象的引用计数都是正值,调用了这四种方法的那段代码要将其中一次保留操作抵消掉。
若方法名不以上述四个词语开头,则表示返回的对象并不归调用者所有。这种情况下,返回的对象会自动释放,所以其值在跨越方法调用边界后依然有效。要使对象多存活一段时间,必须令调用者保留它才行。
除了自动调用保留和释放方法外,它可以执行一些手工操作很难甚至无法完成的优化。如在编译期,ARC会把能够互相抵消的retain、release、autorelease操作约简。如果同一对象执行多次保留和释放操作,ARC有时可以成对的移除这两个操作。
ARC也包含运行期组件。
变量的内存管理语义
ARC也会处理局部变量与实例变量的内存管理。默认情况下,每个变量都是指向对象的强引用。
应用中,可以用修饰符来改变局部变量与实例变量的语义:
-
__strong
默认语义,保留此值 -
__unsafe_unretained
不保留此值,这么做可能不安全,因为等到再次使用变量时,其对象可能已经回收了。 -
__weak
不保留此值,但是变量可以安全使用,因为如果系统把这个对象回收了,那么变量也会自动清空 -
__autoreleasing
把对象按引用传递(pass by Reference)给方法时,使用这个特殊的修饰符,此值在方法返回时自动释放。
我们经常会给局部变量加上修饰符,用以打破块(block)所引入的保留环。块会自动保留其所捕获的全部对象,而如果这其中某个对象又保留了块本身,那么就可能导致保留环。可以用__weak
局部变量打破这种保留环。
block 与 __weak
ARC如何清理实例变量
使用ARC后,不需要编写实例变量dealloc方法,ARC会在dealloc
方法中自动插入释放代码。
ARC会借用Objective-C++的一项特性来生成清理例程(cleanup routine)。回收Objective-C++对象时,待回收的对象会调用所有C++对象的析构函数(destructor)。编译器发现某个对象中含有C++对象,就会生成名为.cxx_destruct
的方法。ARC借助此特性,在该方法中生成清理内存所需的代码。
有非Objective-C对象,如CoreFoundation
中的对象或是由malloc()
分配在堆中的内存,如需要清理。但是不需要想原来那样调用父类dealloc
方法。ARC下,dealloc
方法可以这样写:
- (void)dealloc {
CFRelease(_coreFoundationObject);
free(_heapAllocatedMemoryBlob);
}
ARC会自动生成回收对象时所执行的代码,所以通常无须再编写dealloc
方法。
覆写内存管理方法
不使用ARC时,可以覆写内存管理方法。比如说,在实现单例类的时候,因为单例不可释放,所以经常覆写release
方法,将其替换为空操作(no-op)。
ARC环境下,不能这么做,会干扰到ARC分析对象生命期的工作。而且开发者不可调用及覆写这些方法,所以ARC能够优化retain
、release
、autorelease
操作,使之不经过OC的消息派发机制。
优化后的操作,直接调用隐藏在运行期程序库中的C函数。
- 有ARC后,程序员无需担心内存管理问题。使用ARC变成,可以省去类中的许多样板代码。
- ARC管理对象生命期的办法基本上是:在合适的地方插入保留及释放操作。在ARC环境下,变量的内存管理语义可以通过修饰符指明,而原来则需要手工执行保留及释放操作。
- 有方法所返回的对象,其内存管理语义总是通过方法名来体现。ARC将次确定为开发者必须遵守的规则。
- ARC只负责管理OC对象的内存,尤其要注意:
CoreFoundation
对象不归ARC管理,开发者必须适时调用CFRetain
/CFRelease
。
31、在dealloc方法中只是放引用并解除监听
对象在经历其生命周期后,最终会为系统所回收,这时就要执行dealloc
方法了。在每个对象的生命期内,此方法仅执行一次,就是当引用计数降为0的时候。具体何时执行,无法保证。
dealloc
方法中应该做些什么,主要就是释放对象所拥有的引用,也就是把所有的OC对象都释放掉,ARC会通过自动生成的.cxx_destruct
方法,在dealloc中自动添加这些释放代码。对象中拥有的其他非OC对象也要释放。比如CoreFoundation对象必须手动释放,因为他们是有纯C的API所生成的。
dealloc中还要坐一件事,就是把原来配置过的观测行为(observation behavior)。比如用NSNotificationCenter订阅的通知。一般应该在这里注销。
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
CFRelease(_coreFoundationObject);
}
手动管理引用计数而不用ARC的话,最后还需要调用[super dealloc];同时需要将当前对象所拥有的全部OC对象逐个释放。
虽说应该与dealloc中释放引用,但是开销较大或系统内稀缺的资源不在此列。像是文件描述符(file descriptor)、套接字(socket)、大块内存等,都属于这种资源。不能指望dealloc方法必定会在某个特定的时间调用,因为有一些无法预料的东西可能也持有此函数。那么保留这些稀缺资源的时间有些过长,通常的做法是,实现另一个方法,当应用程序用完资源对象后,就调用此方法,这样一来,资源对象的生命周期就更为明确。
系统并不能保证每个创建出来的对象的dealloc都会执行,所以在清理这些资源的方法应该是其他的清理方法,而非dealloc方法。
应用程序终止后,其占用的资源会返回操作系统,对于一些没有收到dealloc消息的对象,也会消亡。不调用dealloc是为了优化程序效率。而这也说明系统未必会在每个对象上调用其dealloc方法。
在iOS程序中的application delegate中,含有一个会与程序终止时调用的方法。如果一定要清理某些对象,可以在这个方法中调用那些对象的清理方法。
- (void)applicationWillTerminate:(UIApplication *)application
如果对象管理着某些资源,在dealloc中也要调用清理方法。
编写dealloc方法时,不要在里面随便调用其他方法。可以在里面判断是否调用close方法,未调用抛出异常。
调用dealloc那个线程会执行最终的释放操作(final release),令对象引用计数降为0,而某些方法必须在特定线程里执行,在dealloc中调用这些方法,无法保证当前线程是那些方法所需的线程。
dealloc中不要调用属性的存取方法。因为有人可能会覆写这些方法,并于其中做一些无法在回收阶段安全执行的操作。此外,属性可能处于键值观察状态下,该属性的观察者可能会在属性值改变时保留或使用这个即将回收的对象,这种做法会令运行期系统的状态完全失调,从而导致一些莫名其妙的错误。
- 在dealloc方法里,应该做的事情是释放指向其他对象的引用,并取消原来定于的键值观察或NSNotificationCenter等通知,不要做其它事情。
- 如果对象持有文件描述等系统资源,那么应该编写一个方法来释放此种资源。这样的类要和其使用者约定:用完资源后必须调用close方法。
- 执行异步任务的方法不应该在dealloc中调用;只能在正常状态下执行的那些方法不应在dealloc中调用,因为此时对象已经处于正在回收的状态了。
32、编写异常安全代码时留意内存管理问题
当前运行期系统中,C++与OC的异常互相兼容。
OC错误模型表示,异常只应发生在严重错误后抛出。虽说如此,有时仍需要编写代码来捕获并处理异常。
MARC 模式下:
ZYDPersonModel *oPerson = [[ZYDPersonModel alloc] initWithFirstName:@"Json"];
@try {
// 执行可能会有异常的方法
[oPerson personInfo];
} @catch (NSException *exception) {
NSLog(@"Something wrong.");
} @finally {
// 不管是异常,还是正常处理,都要将对象释放
[oPerson release];
}
如果所有对象都要如此释放,就非常乏味。而且假如@try块中的逻辑更为复杂,含有多条语句,那么很容易就会因忘记某个对象导致内存泄露。若泄露的对象是文件描述或数据库链接等稀缺资源,可能引发大问题,这将导致应用程序把所有系统资源都抓在自己手里而不及时释放。
ARC模式下
ZYDPersonModel *personThree = [[ZYDPersonModel alloc] initWithFristName:@"Wellienm" lastName:@"Jone"];
@try {
[personThree performDaysWork];
} @catch (NSException *exception) {
NSLog(@"Something wrong!");
} @finally {
//ARC 下不能使用release,问题更大
}
这种情况ARC不会自动处理,因为这样做需要加入大量样板代码,以便跟踪待清理的对象,从而在抛出异常时将其释放。可是,这段代码会严重影响运行期的性能,即便不抛异常时也是如此。而且添加进来的代码还会明显增加应用程序的大小。
虽说默认状态下未开启,ARC依旧能生成这种安全处理异常所用的附加代买。可以添加-fobjc-arc-exceptions
这个编译器标志来开启此功能。默认不开启的原因是:OC代码中,只有当应用程序必须因异常状况而终止时才应抛出异常,因此,如果应用程序即将终止,是否还会发生内存泄露就已经无关紧要了。在应用程序必须必须立即终止的情况下,还去添加安全处理异常所用的附加代码没有意义。
有种情况编译器会自动把-fobjc-arc-exceptions
标志打开。就是出于Objective-C++模式时。C++处理异常所用的代码与ARC实现的附加代码类似,所以令ARC加入自己的代码以安全处理异常,性能损失不会太大。
如果手动管理引用计数,而且必须捕获异常,那么要设法保证所编代码能把对象正确清理干净。
若使用ARC且必须捕获异常,需要打开编译器的-fobjc-arc-exceptions
标志,但最重要的是:在发现大量异常需要捕获操作时,应考虑重构代码。
- 捕获异常时,一定要注意将try块内所创立的对象清理干净
- 在默认情况下,ARC不生成安全处理异常所需要的清理代码。开启编译器标识后,可生成这种代码,不过会导致应程序变大,而且降低运行效率。
33、以弱引用避免保留环
保留环,就是几个对象都已某种方式互相引用,形成环,即使最有没有别的东西会引用环内的对象,但因对象之间尚有引用,这些引用使他们能继续存活下去,而不会为系统回收。
保留环,会导致内存泄露。
避免保留环的最佳方式就是弱引用。这用引用经常用来表示费拥有关系(nonowning relationship)。将属性声明为unsafe_unretained
即可。
unsafe_unretained
与assign
特质等价,assign
通常只用于整体类型。unsafe_unretained
则用于对象类型。这个词本身就表明所修饰的属可能无法安全使用,不归此实例所拥有。
OC中还要一项与ARC相伴的运行期特性,可以安全使用弱引用:weak
。它与unsafe_unretained
作用相同,但是只要系统把属性回收,属性值就会自动设为nil。
当实例的引用移除后,unsafe_unretained
属性仍指向那个已经回收的实例,而weak
属性则指向nil
。
使用weak而非unsafe_unretained
引用可以令代码更安全。
如果在已经在所至对象已经彻底销毁后还继续使用弱引用,依然是个bug。
一般来说,如果不拥有某对象,就不要保留它。这条规则对collection例外,collection虽然并不直接拥有其内容,但是它要代表自己所属的那个对象来保留这些元素。
有时,对象中的引用会指向另一个并不归于自己所拥有的对象,比如Delegate模式就是这样。
- 将某些引用设为weak,可避免出现保留环,循环引用
- weak引用可以自动清空,也可以不自动清空。自动清空是随着ARC而引入的新特性,有运行期系统来实现。在具备自动清空功能的弱引用上,可以随意读取其数据,因为这种引用不会指向已经回收过的对象。
34、以自动释放池块降低内存峰值
释放对象有两种方式:一种是调用release
方法,使其引用计数立即递减;二是调用autorelease
方法,将其加入自动释放池中。自动释放池用于存放那些需要在稍后某个时刻释放的对象。清空(drain)自动释放池时,系统会向其中的对象发送release
消息。
@autoreleasepool {
//....
}
内存峰值(high-memory waterline)是指应用程序在某个特定时间段内最大内存用量(hightest memeory footprint)。
一般情况下无需担心自动释放池的创建问题。iOS的CocoaTouch系统会自动创建一些线程,比如说主线程或是大中枢派发(Grand Central Dispatch,GCD)机制中的线程,这些线程默认都有自动释放池,每次执行时间循环时,都会将其清空。 因此一般不需要自己创建自从释放池,通常只要一个地方,就是在Main函数里,用自动释放吃包裹应用程序入口点(main application entry point)。
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
从技术角度,不是非得有个自动释放池块才行,因为块的结尾恰好就是应用程序的终止处,此时操作系统会把程序所占的内存都释放掉。话虽如此,但是如果不写这个块的话,那么由UIApplicationMain函数所自动释放的那些对象,就没有自动释放池可以容纳了,于是系统会发出警告信息来说明这一情况。所以说,这个池可以理解成最外围捕捉全部自动释放对象所用的池。
自动释放池可以嵌套,嵌套的好处是可以借此控制应用程序的内存峰值,使其不致过高。
NSMutableArray *people = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; i ++) {
@autoreleasepool {
ZYDPersonModel *model = [[ZYDPersonModel alloc] initWithFristName:@"Json" lastName:[NSString stringWithFormat:@"%d",i]];
[people addObject:model];
}
}
有时如果会创建很多不必要的本来应该提早回收的临时对象,增加一个自动释放池可以解决此问题。把循环内的代码包裹在自动释放池块中,那么循环中的自动释放对象就会放在这个池,而不是线程的主池里面。加上这个自动释放池之后,应用程序在执行循环时的内存峰值就会降低。
自动释放池机制就像栈一样,系统创建好自动释放池后,就将其推入栈中,而清空自动释放池,则相当于将其从栈中弹出。在对象上执行自动释放操作,就等于将其放入栈顶的那个池里。
是否用池来右滑效率,完全取决于具体应用程序。首先监控内存永亮,判断其中有没有需要解决的问题,如果没完成这一步,就不着急优化。尽管自动释放池块的开销不大,但毕竟还是有的,尽量不要建立额外的自动释放池。
- 自动释放池排布在栈中,对象收到autorelease消息后,系统将其放入最顶端的池里。
- 合理运用自动释放池,可降低应用程序的内存峰值。
- @autoreleasepool能创建出更为轻便的自动释放池。
35、用僵尸对象调试内存管理问题
问题:向已经回收的对象发送消息是不安全的。这么做有时可以,有时不行。取决于对象所占内存是否被其他内容所覆写,但是这个又无法确认。因此程序只是偶尔崩溃。
僵尸对象(Zombie Object),Cocoa提供的功能。启动这项调试功能之后,运行期系统会把所有已经回收的实例转化成僵尸对象,而不会真正回收他们。这种对象所在的核心内存无法重用个,因此不可能遭到覆写。僵尸对象收到消息后,会抛出异常,其中准确说明了发送过来的消息,并描述回收之前的那个对象。
僵尸对象是调试内存管理问题的最佳方式。
打开僵尸对象调试:
给僵尸对象发送消息后,控制台会打印消息,应用程序则会终止。
ZYDPersonModel *person = [[ZYDPersonModel alloc] initWithFirstName:@"Json"];
[person release];
//释放之后再发送消息
NSLog(@"%@",[person personInfo]);
** -[ZYDPersonModel release]: message sent to deallocated instance 0x608000014e90
通过object_getClass()
方式获取类名,一个对象在释放之前,以及释放转变成僵尸对象之后,类型发生变化。
// 原数据类型
=== ZYDPersonMode
// 成为僵尸对象之后
=== _NSZombie_ZYDPersonMode
_NSZombie_ZYDPersonMode
是在运行期生成的,当首次碰到ZYDPersonMode类的对象要变成僵尸对象时,就会创建这么一个类,创建过程中用到了运行期程序块里的函数,它们的功能强大,可以操作类列表(class list)。
崩溃日志中,如果出现类似字段,就说明是僵尸对象的问题。
僵尸类(zombie clas)是从名为_NSZombie_
的模板类里复制出来的。这些僵尸类没有多少事情可做,只是充当一个标记。
运行期系统如果发现NSZombieEnabled环境变量已设置,那么久吧dealloc方法调配成一个会执行特定方法的版本,执行到程序末尾,对象所述的类就变成_NSZombie_OriginalClass
了,其中OriginalClass
指的是原类名。
对于僵尸对象而言,只是个调试手段,制作正式发行的应用程序时不会把这项功能打开。
僵尸类的作用会在消息转发例程中体现出来。_NSZombie_
类(以及所用从该类拷贝出来的类)并未实现任何方法。此类没有超类,和NSObject一样是根类,该类只有一个实例变量,叫做isa,所有OC的根类必须有此变量。由于这个轻量级的类没有实现任何方法,所以发给它的全部消息都要经过完整的消息转发机制。
在完整的消息转发机制中,__forwarding__
是核心,调试程序时,可能在栈回溯消息里看过这个类。它首先要做的就包括检查接收消息的对象所属的类名。若类名前缀是_NSZombie_
,则表示消息接收者是僵尸对象,需要特殊处理。
- 系统在回收对象时,可以不将其真的回收,而是把他转化为僵尸对象。通过环境变量NSZombieEnabled可开启此功能。
- 系统会修改对象的isa指针,令其指向特殊的僵尸类,从而使该对象变为僵尸对象。僵尸类能相应所有的选择子,相应方式为:打印这一条包含信息内容及其接收者的消息,然后终止应用程序。
36、不要使用retainCount
OC通过引用计数来管理内存。NSObject协议定义了方法由于查询对象当前的引用计数:
- (NSUInteger)retainCount;
这个方法在ARC中被废弃了,即使在MARC中还是有一些问题:
- 它返回的引用计数只是某个时间点上的值,该方法并未考虑到系统会稍后把自动释放池清空,因而不会将后续的释放操作从返回值里减去,这样的话,这个值就未必能够真实反映实际的引用计数。
- 如果通过不停地释放操作降低引用计数,直至对象被系统回收。加入此对象也在自动释放池里,那么稍后系统清空池子的时候还要把它释放一次,将导致崩溃。
- retainCount可能永远不返回0,以为又是系统会优化对象的释放行为,在引用计数还是1的时候就把它回收了。只有在系统不打算这么优化时,计数值才会递减至0.
NSString *string1 = @"string1";
NSString *string2 = [[NSString alloc] initWithFormat:@"string2"];
NSNumber *numberI = @2;
NSNumber *numberF = @3.14f;
NSLog(@"retainCount : %lu",[string1 retainCount]);
// retainCount : 18446744073709551615
NSLog(@"retainCount : %lu",[string2 retainCount]);
// retainCount : 18446744073709551615
NSLog(@"retainCount : %lu",[numberI retainCount]);
// retainCount : 9223372036854775807
NSLog(@"retainCount : %lu",[numberF retainCount]);
// retainCount : 1
第一、二个对象的引用计数(2^64 - 1),第三个引用计数(2^63 - 1)。三者都是单例对象(singleton object),所以引用计数都很大。系统会尽可能把NSString实现成为单例对象。
如果字符串像这里这样,是个编译器常量(compile-time constant),那么就可以这样来实现了。这种情况下,编译器会把NSString对象所表示的数据放到应用程序的二进制文件中,这样,运行程序时就可以直接用了,无需再创建NSString对象。
NSNumber也雷系,它使用了标签指针(tagged pointer)的概念来标注特定类型的数值。这种做法不使用NSNumber对象,而是把与数值有关的全部消息都放在指针里面。运行期系统会在消息派发期间检测到这种标签指针,并对它进行相应操作,使其行为看上去和真正的NSNumber对象一样。这种优化只在某些场合使用,这里的浮点数就没有优化,其引用计数就是1。
对于单例对象,它的引用计数不会变,这种对象的保留及释放操作都是空操作(no-op)。即便两个单例对象之间,引用计数也各不相同。
系统对引用计数的处理方式一再声明:不应该总是依赖引用计数的具体值来编码。
即便只是测试,retainCount的值也不是很有用,由于对象可能处于自动释放池中,其他程序库中也有可能自行保留或释放对象,这都会扰乱引用计数的具体取值。
绝对不要用retainCount,尤其是ARC之后已经正式将其废弃,就跟不应该用了。
- 对象的引用计数看似有用,实则不然,因为任何给定时间点上的绝对引用计数(absolute retain count)都无法反映对象生命周期的全貌。
- ARC,retainCount方法正式废止,ARC下调用会导致编译器报错。