什么网站可以做章,请人帮忙做网站推广,大连网站怎么推广,电商网站建设与维护试题【Effective Objective - C】—— 内存管理 前言29.理解引用计数引用计数工作原理关闭ARC模式属性存取方法中的内存管理自动释放池保留环要点 30.以ARC简化引用计数使用ARC时必须遵守的方法命名规则变量的内存管理语义ARC如何清理实例变量要点 31.在dealloc方法中只释放引用并解… 【Effective Objective - C】—— 内存管理 前言29.理解引用计数引用计数工作原理关闭ARC模式属性存取方法中的内存管理自动释放池保留环要点 30.以ARC简化引用计数使用ARC时必须遵守的方法命名规则变量的内存管理语义ARC如何清理实例变量要点 31.在dealloc方法中只释放引用并解除监听要点 32.编写“异常安全代码”时留意内存管理问题要点 33.以弱引用避免保留环要点 34.以自动释放池块降低内存峰值内存峰值要点 35.用僵尸对象调试内存管理问题僵尸对象的工作原理僵尸类是怎么产生的要点 36.不要使用retainCount被弃用的retainCount要点 前言
OC是一门面向对象的语言而面向对象语言里内存管理是一个重要的概念想用一门语言写出内存使用效率高的代码就得掌握其中的细节在OC引入ARC模式之后内存管理的事情几乎都是由编译器来决定的这使我们学习内存管理变得简单许多。
29.理解引用计数
Objective-C 语言使用引用计数来管理内存也就是说每个对象都有个可以递增或递减的计数器如果某个对象引用他时就会给其引用计数加1用完了之后就递减其计数直至为0销毁这个对象。ARC实际上也是一种引用计数机制。
引用计数工作原理
在引用计数架构下对象有个计数器用以表示当前有多少个事物想令此对象继续存活下去。NSObject协议声明了下面三个方法用于操作计数器
retain 递增保留计数release 递减保留计数autorelease 待稍后清理“自动释放池”时再递减保留计数。
查看保留计数的方法叫做retainCount这个方法不太有用随后会解释 通常我们都使用alloc方法来创建一个对象给对象一个继续存活下去的意愿但是使用alloc方法创建的对象不一定其引用计数创建出来就是1我们只能说明保留计数至少为1。并且我们如果在对象的引用计数为0的情况下即其“可复用”的情况下使用这个变量的话程序就会崩溃。但是若是将其放到了 “自动释放池” 中程序就可能不会崩溃了。所以我们为了避免这种情况的发生我们通常就将对象释放完后将其置空这种指针通常称为“悬挂指针”。
关闭ARC模式
在buliding settings 里的Levels项中的Automatic Reference Counting设置为NO
属性存取方法中的内存管理
如前所述对象图由互相关联的对象所构成。刚才那个例子中的数组通过在其元素上调 用retain 方法来保留那些对象。不光是数组其他对象也可以保留别的对象这 一般通过访 问“ 属性” (参见第6 条)来实现而访问属性时会用到相关实例变量的获取方法及设置方法。若属性为“ strong 关系” (strong relationship)则设置的属性值会保留。比方说有个名 叫foo 的属性由名为_foo 的实例变量所实现那么该属性的设置方法会是这样:
- (void)setFoo:(id)foo {[foo retain];[_foo release];_foo foo;
}此方法将保留新值并释放旧值然后更新实例变量令其指向新值。顺序很重要。假如 还未保留新值就先把旧值释放了而且两个值又指向同 一个对象那么先执行的release 操 作就可能导致系统将此对象永久回收。而后续的retain 操作则无法令这个已经彻底回收的对 象复生 于是实例变量就成 了悬挂指针。
自动释放池
在Objective-C 的引用计数架构中自动释放池是一项重要特性。调用release 会立刻递 减对象的保留计数 ( 而且还有可能令系统回收此对象)然而有时候可以不调用它改为调用 autorelease此方法会在稍后递减计数通常是在下一次“事件循环” (event loop)时递减 不过也可能执行得更早些。 此特性很有用尤其是在方法中返回对象时更应该用它。在这种情况下我们并不总是 想令方法调用者手工保留其值。比方说有下面这个方法:
- (NSString *)stringValue {NSString *str [[NSString alloc] initWithFormat:I am this:%, self];return str;
}
此时返回的str对象其保留计数比期望的要多1因为alloc会令保留计数加1而又没有与之对应的释放操作这就会有很大的影但是在何处释放就又是问题了因为其还的返回返回完后又获取不到这个str也不能在返回之后释放所以此时就用到了autorelease在其返回后保留一段时间再释放。
- (NSString *)stringValue {NSString *str [[NSString alloc] initWithFormat:I am this:%, self];return [str autorelease];
}
实际上释放操作会在清空最外层的自动释放池时执行除非你有自己的自动释放池否则这个时机指的就是当前线程的下一次事件循环。 通过上述可见autorelease能延长对象生命期使其在跨越方法调用边界后依然可以存活一段时间。
保留环
保留环其实就是因为对象之间的互相引用出现的问题这会导致内存泄漏因为循环中的对象其保留计数不会降为0。 我们要解决保留环通常采用“弱引用”来解决此问题或者从外界命令循环中的某个对象不在保留另一个对象。
要点
引用计数机制通过可以递增递减的计数器来管理内存。对象创建好之后其保留计数至少为1。若保留计数为正则对象继续存活。当保留计数降为0时对象就被销毁了。在对象生命期中其余对象通过引用来保留或释放此对象。保留与释放操作分别会递增及递减保留计数。
30.以ARC简化引用计数
使用ARC时一定要记住引用计数实际上 还是要执行的只不过 保 留 与 释 放 操 作 现 在 是由ARC 自动为你添加。稍后将会看到除了为方法所返回的对象正确运用内存管理语义 之外ARC 还有更多的功能。 不过ARC 的那些功能都是基 于核心的内存管理语义而构建的 这套标准语义贯穿于整 个Objective-C语言。
由于ARC会自动执行retain 、release 、autorelease 等操作所以直接在ARC下调用这些 内存管理方法是非法的。具体来说不能调用下列方法:
retainreleaseautoreleasedealloc
实际上ARC 在调用这些方法时并不通过普通的Objective-C 消息派发机制而是直 接调用其底层C 语言版本。这样做性能更好因为保留及释放操作需要频繁执行所以直 接 调 用 底 层 丽 数 能 节省很多CPU周期。比方说ARC会调用与retain等价的底层丽数 objc_retain。这也是不能覆写retain、release 或autorelease 的缘由因为这些方法从来不会被直接 调用。笔者在本节后面的文字中将用等价的Objective-C 方法来指代与之相关的底层C语言 版本这对 于那些 手动管理过引用计数的开发者来说更易理解。
使用ARC时必须遵守的方法命名规则
若方法名以下列词语开头则其返回的对象归调用者所有
allocnewcopymutableCopy
意思就是调用这四种方法的那段代码要负责释放方法所返回的对象。也就是说这些对象的保留计数是正值而调用了这四种方法的那段代码要将其中一次保留操作抵消。若方法名不以上述四个词语开头则表示其所返回的对象并不归调用者所有。这种情况下返回的对象会自动释放,也就是使用了autorelease。
ARC除了自动调用“保留”和“释放”方法外其也可以优化操作比如两个在一起的保留和释放它就会将这一对直接移除不执行这两个代码。
在ARC环境下编译代码时必须考虑“向后兼容性”以兼容那些不使用ARC的代码其实ARC的简化操作是因为其调用的特殊函数它会把autorelease方法改为调用objc_autoreleaseReturnValue函数把retain方法改为objc_retainAutoreleaseReturnValue函数。 下列代码演示了ARC的用法∶ (EOCPerson *)newPerson {EOCPerson *person [[EOCPerson alloc] init];return person;
} (EOCPerson *)somePerson {EOCPerson *person [[EOCPerson alloc] init];return person;
}- (void)doSomething {EOCPerson *personOne [EOCPerson newPerson];EOCPerson *personTwo [EOCPerson somePerson];
}
ARC通过命名约定将内存管理规则标准化初学此语言的人通常觉得这有些奇怪其他编程语言很少像 Objective-C这样强调命名。但是想成为优秀的 Objective-C程序员就必须适应这套理念。在编码过程中ARC 能帮程序员做许多事情。 除了会自动调用保留与释放方法外使用ARC还有其他好处它可以执行一些手工操作很难甚至无法完成的优化。 例如在编译期ARC会把能够互相抵消的 retain、release、autorelease 操作约简。如果发现在同一个对象上执行了多次保留与释放操作那么 ARC有时可以成对地移除这两个操作。
变量的内存管理语义
ARC也会处理局部变量与实例变量的内存管理。通常情况下每个变量都是指向对象的强引用。 就用set方法来说不使用ARC而自己设置set方法就需要这样写
- (void)setObject:(id)object {[_object release];_object [object retain];
}
但是这样写会出现问题如果新值和实例变量已有的值相同了它再执行release就会将其释放其保留计数若降为0后来再进行retain保留操作程序就会报错而使用ARC仅仅需要这样就够了
- (void)setObject:(id)object {_object object;
}
ARC会用一种安全的方式来设置先保留新值再释放旧值最后设置实例变量。 我们通常会给局部变量加上修饰符用以打破由“块”所引入的“保留环”。块会自动保留其所捕获的全部对象而如果这其中有某个对象又保留了块本身那么就可能导致“保留环”。
在应用程序中可用以下修饰符来改变局部变量与实例变量的语义
__strong: 默认语义保留此值。__unsafe_unretained:不保留此值这么做可能不安全因为等到再次使用变量时其对象可能己经回收了。__weak:不保留此值但是变量可以安全使用因为如果系统把这个对象回收了 那么变量也会自动清空。__autoreleasing:把对象“按引用传递”给方法时使用这个特殊 的修饰符 。此值在方法返回时自动释放。
ARC如何清理实例变量
要管理其内存ARC就必须在“回收分配给对象的内存”是生产必要的清理代码。ARC环境下dealloc方法可以这样来写
- (void)dealloc {CFRelease ( _coreFoundationObject);free ( _heapAllocatedMemoryBlob);
}
因为ARC会自动生成回收对象时所执行的代码所以通常无需再编写dealloc方法。这能减少项目源代码的大小而且可以省去其中一些样板代码。
要点
有ARC之后程序员就无须担心内存管理问题了。使用ARC来编程可省去类中的许多“样板代码”。ARC管理对象生命期的办法基本上就是在合适的地方插入“保留”及“释放”操作。在ARC环境下变量的内存管理语义可以通过修饰符指明而原来则需要手工执行“保留”及“释放”操作。由方法所返回的对象其内存管理语义总是通过方法名来体现。ARC将此确定为开发者必须遵守的规则。ARC只负责管理OC对象的内存。尤其要注意CoreFoundation对象不归ARC管理开发者必须适时调用CFRetain/CFRelease。
31.在dealloc方法中只释放引用并解除监听
对象在经历其生命期后最终会为系统所回收这时就要执行dealloc 方法了。在每个 对象的生命期内此方法仅执行一次也就是当保留计数降为。的时候。然而具体何时执 行则无法保证。也可以理解成:我们能够通过人工观察保留操作与释放操作的位置来预 估此方法何时即将执行。但实际 上程序库会以开发者察觉不到的方式操作对象从而使回 收对象的真正时机和预期的不同。你决不应该自己调用dealloc 方法。运行期系统会在适当 的时候调用它。而且 一旦调用过dealloc之后对象就不再有效了后续方法调用均是无效的。
那么应该在dealloc 方法中做些什么呢?主要就是释放对象所拥有的引用也就是把 所 有 Objective-C对象都释放掉ARC会通过自动生成的.cxx_destruct方法 (参见第30条) 在dealloc 中为你自动添加这些释放代码。对象所拥有的其他非Objective-C 对象也要释放。 比如CoreFoundation 对象就必领手工释放因为它们是由纯C的API 所生成的。
在dealloc 方法中通常还要做一件事那就是把原来配置过的观测行为(observation behavior )都清理掉。如果用NSNotificationCenter 给此对象订阅(register)过某种通知那么 一般应该在这里注销(unregister )这样的话通知系统就不再把通知发给回收后的对象了 若是还向其发送通知则必然会令应用程序崩溃。
dealloc 方法可以这样来写:
- (void)dealloc {CFRelease(coreFoundationObject);[[NSNotificationCenter defaultCenter] removeObserver:self];
}
要点
在dealloc方法里应该做的事情就是释放指向其他对象的引用并取消原来订阅的“键值观测”或“NSNotificationCenter”等通知不要做其他事情。如果对象持有文件描述符等系统资源那么应该专门编写一个方法来释放此种资源。这样的类要和其使用者约定用完资源后必须调用close方法。执行异步任务的方法不应在dealloc里调用只能在正常状态下执行的那些方法也不应在dealloc里调用因为此时对象已处于正在回收的状态了。
32.编写“异常安全代码”时留意内存管理问题
许多时下流行的编程语言都提供了异常exception这一特性。纯C中没有异常而C与Objective-C都支持异常。实际上在当前的运行期系统中C与Objective-C的异常相互兼容也就是说从其中一门语言里抛出的异常能用另外一门语言所编的异常处理程序exception handler来捕获。 Objective-C的错误模型表明异常只应在发生严重错误后抛出参见第 21条虽说如此不过有时仍然需要编写代码来捕获并处理异常。比如使用Objective-C来编码时或是编码中用到了第三方程序库而此程序库所抛出的异常又不受你控制时就需要捕获及处理异常了。此外有些系统库也会用到异常这使我们想起从前那个频繁使用异常的年代。比如在使用键值观测KVO功能时若想注销一个尚未注册的观察者便会抛出异常。 发生异常时应该如何管理内存是个值得研究的问题。在 try 块中如果先保留了某个对象然后在释放它之前又抛出了异常那么除非 catch块能处理此问题否则对象所占内存就将泄漏。C的析构函数destructor由 Objective-C 的异常处理例程exception-handle routine来运行。这对于C对象很重要由于抛出异常会缩短其生命期所以发生异常时必须析构不然就会泄漏而文件句柄file handle等系统资源因为没有正确清理所以就更容易因此而泄漏了。 异常处理例程将自动销毁对象然而在手动管理弓用计数时。销毁工作有些麻烦。以下面这段使用手工引用计数的 Objective-C代码为例∶
try {EOCSomeClass *object [ [EOCSomeClass alloc] init];[object doSomethingThatMayThrow];[object release];
}
catch(...) {NSLog(whoops,there was an error. Oh well...);
}
乍一看似乎没问题但如果 doSomethingThatMayThrow抛出异常了呢?由于异常会令执行过程终止并跳至 catch 块因而其后的那行 release 代码不会运行。在这种情况下如果代码抛出异常那么对象就泄漏了。这么做不好。解决办法是使用finally 块无论是否抛出异常其中的代码都保证会运行且只运行一次。刚才那段代码可改写如下
EOCSomeClass *object;
try {object [ [EOCSomeClass alloc] init];[object doSomethingThatMayThrow] ;
}
catch (...){NSLog (whoops,there was an error.Oh well...);
}
finally {[object release];
}
注意由于finally 块也要引用object 对象所以必须把它从 try 块里移到外面去。要是所有对象都得如此释放那这样做就会非常乏味。而且假如 try 块中的逻辑更为复杂含有多条语句那么很容易就会因为忘记某个对象而导致泄漏。若泄漏的对象是文件描述符或数据库连接等稀缺资源或是这些稀缺资源的管理者则可能引发大问题因为这将导致应用程序把所有系统资源都抓在自己手里而不及时释放。 在 ARC 环境下问题会更严重。下面这段使用ARC 的代码与修改前的那段代码等效∶
try {EOCSomeClass *object [[EOCSomeClass alloc] init];[object doSomethingThatMayThrow ];
}
catch (...) {NSLog(whoops,there was an error. Oh well...);
}
现在问题更大了∶由于不能调用release所以无法像手动管理引用计数时那样把释放操作移到 finally 块中。你可能认为这种状况 ARC自然会处理的。但实际上 ARC不会自动处理因为这样做需要加入大量样板代码以便跟踪待清理的对象从而在抛出异常时将其释放。可是这段代码会严重影响运行期的性能即便在不抛异常时也如此。而且添加进来的额外代码还会明显增加应用程序的大小。这些副作用都不甚理想。
要点
捕获异常时一定要注意将 try 块内所创立的对象清理干净。在默认情况下ARC不生成安全处理异常所需的清理代码。开启编译器标志后可生成这种代码不过会导致应用程序变大而且会降低运行效率。
33.以弱引用避免保留环
先来看看保留环的概念就是几个对象都以某种方式互相引用从而形成环cycle。 由于 Objective-C 内存管理模型使用引用计数架构所以这种情况通常会泄漏内存因为最后没有别的东西会引用环中的对象。这样的话环里的对象就无法为外界所访问了但对象之间尚有引用这些引用使得它们都能继续存活下去而不会为系统所回收。 最简单的保留环由两个对象构成它们互相引用对方。下图举例说明了这种情况。这种保留环的产生原因不难理解且很容易就能通过查看代码而侦测出来∶ 举个例子
#importFoundation/Foundation.h
class EOCClassA;
class EOCClassB;interface EOCClassA : NSObject
pzoperty(nonatomic,strong) EOCClassB *other;
endinterface EOCClassB : NSObject
property(nonatomic,strong) EOCClassA *other;
end 避免保留环的最佳方式就是弱引用。这种引用经常用来表示非拥有关系nonowning relationship。将属性声明为 unsafe_unretained 即可。修改刚才那段范例代码将其属性声明如下∶
#import Foundation/Foundation.hclass EOCClassA;
class EOCClassB;interface EOCClassA : NSObject
property(nonatomic,strong)EOCClassB *other;
endinterface EOCClassB: NSObject
property(nonatomic,unsafe unretained) EOCClassA *other;
end#import Foundation/Foundation.hclass EOCClassA;
class EOCClassB;interface EOCClassA : NSObject
property(nonatomic,strong)EOCClassB *other;
endinterface EOCClassB: NSObject
property(nonatomic,unsafe unretained) EOCClassA *other;
end
修改之后EOCClassB 实例就不再通过 other 属性来拥有 EOCClassA实例了。属性特质attribute中的 unsafe unretained一词表明属性值可能不安全而且不归此实例所拥有。如果系统已经把属性所指的那个对象回收了那么在其上调用方法可能会使应用程序崩溃。由于本对象并不保留属性对象因此其有可能为系统所回收。 用 unsafe unretained修饰的属性特质其语义同 assign特质等价参见第6条。然而assign通常只用于整体类型int、float、结构体等unsafe unretained 则多用于对象类型。这个词本身就表明其所修饰的属性可能无法安全使用unsafe。 Objective-C中还有一项与ARC 相伴的运行期特性可以令开发者安全使用弱引用∶这就是weak属性特质它与unsafe unretained 的作用完全相同。然而只要系统把属性回收属性值就会自动设为 nil。在刚才那段代码中EOCClassB的 other属性可修改如下∶
property (nonatomic,weak) EOCClassA *other;
下图演示了 unsafe unretained与 weak 属性的区别 当指向 EOCClassA 实例的引用移除后unsafe unretained属性仍然指向那个已经回收的实例而weak 属性则指向 nilweak在开发中多用于协议
要点
将某些引用设为 weak可避免出现保留环。weak 引用可以自动清空也可以不自动清空。自动清空autonilling是随着 ARC 而引入的新特性由运行期系统来实现。在具备自动清空功能的弱引用上可以随意读取其数据因为这种引用不会指向已经回收过的对象。
34.以自动释放池块降低内存峰值
Objective-C对象的生命期取决于其引用计数参见第29条。在 Objective-C的引用计数架构中有一项特性叫做自动释放池autorelease pool。释放对象有两种方式;一种是调用release 方法使其保留计数立即递减;另一种是调用 autorelease 方法将其加入自动释放池中。自动释放池用于存放那些需要在稍后某个时刻释放的对象。清空drain自动释放池时系统会向其中的对象发送 release 消息。 创建自动释放池所用语法如下∶
autoreleasepool {// ...
}
内存峰值
内存峰值是指应用程序在某个特定时间段内的最大内存用量。而自动释放池可以减少这个峰值因为系统会在块的末尾把某些对象回收掉而刚才提到的那种临时对象就在回收之列。自动释放池机制就像栈一样系统创建好自动释放池之后就将其堆入栈中而清空自动释放池的过程就是把其从栈弹出在对象上指向自动释放操作就是将其放入栈顶的池里。但是尽管自动释放池块的开销不太大但毕竟还是有的貌似有尽量不要建立额外的自动释放池。autoreleasepool语法还有个好处每个自动释放池均有其范围可以避免无意间误用了那些在清空池后以为系统所回收的对象。
然而一般情况下无须担心自动释放池的创建问题。Mac OS X与 iOS 应用程序分别运行于Cocoa 及 Cocoa Touch 环境中。系统会自动创建一些线程比如说主线程或是大中枢派发Grand Central DispatchGCD③机制中的线程这些线程默认都有自动释放池每次执行事件循环event loop时就会将其清空。因此不需要自己来创建自动释放池块。通常只有一个地方需要创建自动释放池那就是在 main 函数里我们用自动释放池来包裹应用程序的主入口点main application entry point。比方说iOS程序的 main 函数经常这样写∶
int main(int argc,char *argv[]) {autoreleasepool {return UIApplicationMain (argc, argV, ni1, EOCAppDelegate);}
}
从技术角度看不是非得有个自动释放池块才行。因为块的末尾恰好就是应用程序的终止处而此时操作系统会把程序所占的全部内存都释放掉。虽说如此但是如果不写这个块的话那么由 UIApplicationMain 函数所自动释放的那些对象就没有自动释放池可以容纳了于是系统会发出警告信息来表明这一情况。所以说这个池可以理解成最外围捕捉全部自动释放对象所用的池。 下面这段代码中的花括号定义了自动释放池的范围。自动释放池于左花括号处创建并干对应的右花括号处自动清空。位于自动释放池范围内的对象。将在此范围末尾处收到release消息。自动释放池可以嵌套。系统在自动释放对象时会把它放到最内层的池里。比方说∶
autoreleasepool {NSString *string [NSString stringwithFormat:1 %i,1];autoreleasepool {NSNumber *number [NSNumber numberWithInt:1];}
}
本例中有两个对象它们都由类的工厂方法所创建这样创建出来的对象会自动释放参见第30条。NSString 对象放在外围的自动释放池中而 NSNumber 对象则放在里层的自动释放池中。将自动释放池嵌套用的好处是可以借此控制应用程序的内存峰值使其不致过高。
要点
自动释放池排布在栈中对象收到autorelease消息后系统将其放入最顶端的池里。合理运用自动释放池可降低应用程序的内存峰值。合理运用自动释放池可降低应用程序的内存峰值。autoreleasepool这种新式写法能创建出更为轻便的自动释放池。
35.用僵尸对象调试内存管理问题
Cocoa提供了一个名为僵尸对象的功能启用这项功能之后运行期系统会把所有已经回收的实例对转化为特殊的僵尸对象而不会真正的回收。僵尸对象虽然不能重新使用但是当我们给僵尸对象发送消息的时候会抛出异常还能准确的说明了发送过来的信息是如何 也可以在xcode打开
僵尸对象的工作原理
系统在即将回收对象时如果发现通过环境变量启用了僵尸对象功能那么还将执行一个附加步骤。这一步就是把对象转化为僵尸对象而不彻底回收。 僵尸对象的打印效果可以明确指出僵尸对象收到的选择子及其原来所属的类其中还包含了接受消息的僵尸对象所对应的指针值这个消息在适当的时候用处很大
僵尸类是怎么产生的
他其实是在运行期生成的当首次碰到一个类的对象要变成僵尸对象时它就会创建这么一个类。僵尸类是从名为_NSZombie_的模版里复制出来的这些僵尸类没有多少事情可做只是一个标记又因为它将原类的方法都拷贝了所以它会响应原类的方法不过它会报错提醒程序员。
要点
系统在回收对象时可以不将其真的回收而是把它转化为僵尸对象。通过环境变量NSZombieEnabled 可开启此功能。系统会修改对象的 isa 指针令其指向特殊的僵尸类从而使该对象变为僵尸对象。僵尸类能够响应所有的选择子响应方式为∶打印一条包含消息内容及其接收者的消息然后终止应用程序。
36.不要使用retainCount
被弃用的retainCount
OC的每一个对象都有一个计数器并且计数器的值表明还有多少个其他对象想令此对象继续存活。NSObject协议存在一个方法可以查询当前的保留计数。 ARC forbids implementation of ‘retainCount’在ARC模式下该方法已经被弃用了首要原因还是它返回的保留计数只是某个对象在某个具体时间的引用计数完全没有特殊的参考意义现在不仅存在自动释放池还有ARC模式的自动管理引用计数那么retainCOunt完全不能反映真实的引用计数。
要点
对象的保留计数看似有用实则不然因为任何给定时间点上的“绝对保留计数”都无法反映对象生命期的全貌。引入ARC之后retainCount方法就正式废止了在ARC下调用该方法会导致编译器报错。